Bump github.com/nats-io/nats-server/v2 from 2.9.19 to 2.9.21
Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.9.19 to 2.9.21. - [Release notes](https://github.com/nats-io/nats-server/releases) - [Changelog](https://github.com/nats-io/nats-server/blob/main/.goreleaser.yml) - [Commits](https://github.com/nats-io/nats-server/compare/v2.9.19...v2.9.21) --- updated-dependencies: - dependency-name: github.com/nats-io/nats-server/v2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
committed by
Ralf Haferkamp
parent
0aafeccb93
commit
6d55325a41
@@ -55,7 +55,7 @@ require (
|
|||||||
github.com/libregraph/lico v0.60.1-0.20230811070109-1d4140be554d
|
github.com/libregraph/lico v0.60.1-0.20230811070109-1d4140be554d
|
||||||
github.com/mitchellh/mapstructure v1.5.0
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
|
||||||
github.com/nats-io/nats-server/v2 v2.9.19
|
github.com/nats-io/nats-server/v2 v2.9.21
|
||||||
github.com/oklog/run v1.1.0
|
github.com/oklog/run v1.1.0
|
||||||
github.com/olekukonko/tablewriter v0.0.5
|
github.com/olekukonko/tablewriter v0.0.5
|
||||||
github.com/onsi/ginkgo v1.16.5
|
github.com/onsi/ginkgo v1.16.5
|
||||||
@@ -238,7 +238,7 @@ require (
|
|||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/juliangruber/go-intersect v1.1.0 // indirect
|
github.com/juliangruber/go-intersect v1.1.0 // indirect
|
||||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||||
github.com/klauspost/compress v1.16.5 // indirect
|
github.com/klauspost/compress v1.16.7 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.1.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.1.0 // indirect
|
||||||
github.com/libregraph/oidc-go v1.0.0 // indirect
|
github.com/libregraph/oidc-go v1.0.0 // indirect
|
||||||
github.com/longsleep/go-metrics v1.0.0 // indirect
|
github.com/longsleep/go-metrics v1.0.0 // indirect
|
||||||
@@ -266,7 +266,7 @@ require (
|
|||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/mschoch/smat v0.2.0 // indirect
|
github.com/mschoch/smat v0.2.0 // indirect
|
||||||
github.com/nats-io/jwt/v2 v2.4.1 // indirect
|
github.com/nats-io/jwt/v2 v2.4.1 // indirect
|
||||||
github.com/nats-io/nats.go v1.27.0 // indirect
|
github.com/nats-io/nats.go v1.28.0 // indirect
|
||||||
github.com/nats-io/nkeys v0.4.4 // indirect
|
github.com/nats-io/nkeys v0.4.4 // indirect
|
||||||
github.com/nats-io/nuid v1.0.1 // indirect
|
github.com/nats-io/nuid v1.0.1 // indirect
|
||||||
github.com/nxadm/tail v1.4.8 // indirect
|
github.com/nxadm/tail v1.4.8 // indirect
|
||||||
|
|||||||
@@ -1402,8 +1402,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
|
|||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE=
|
github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE=
|
||||||
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||||
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
|
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
|
||||||
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
@@ -1553,10 +1553,10 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW
|
|||||||
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8=
|
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8=
|
||||||
github.com/nats-io/jwt/v2 v2.4.1 h1:Y35W1dgbbz2SQUYDPCaclXcuqleVmpbRa7646Jf2EX4=
|
github.com/nats-io/jwt/v2 v2.4.1 h1:Y35W1dgbbz2SQUYDPCaclXcuqleVmpbRa7646Jf2EX4=
|
||||||
github.com/nats-io/jwt/v2 v2.4.1/go.mod h1:24BeQtRwxRV8ruvC4CojXlx/WQ/VjuwlYiH+vu/+ibI=
|
github.com/nats-io/jwt/v2 v2.4.1/go.mod h1:24BeQtRwxRV8ruvC4CojXlx/WQ/VjuwlYiH+vu/+ibI=
|
||||||
github.com/nats-io/nats-server/v2 v2.9.19 h1:OF9jSKZGo425C/FcVVIvNgpd36CUe7aVTTXEZRJk6kA=
|
github.com/nats-io/nats-server/v2 v2.9.21 h1:2TBTh0UDE74eNXQmV4HofsmRSCiVN0TH2Wgrp6BD6fk=
|
||||||
github.com/nats-io/nats-server/v2 v2.9.19/go.mod h1:aTb/xtLCGKhfTFLxP591CMWfkdgBmcUUSkiSOe5A3gw=
|
github.com/nats-io/nats-server/v2 v2.9.21/go.mod h1:ozqMZc2vTHcNcblOiXMWIXkf8+0lDGAi5wQcG+O1mHU=
|
||||||
github.com/nats-io/nats.go v1.27.0 h1:3o9fsPhmoKm+yK7rekH2GtWoE+D9jFbw8N3/ayI1C00=
|
github.com/nats-io/nats.go v1.28.0 h1:Th4G6zdsz2d0OqXdfzKLClo6bOfoI/b1kInhRtFIy5c=
|
||||||
github.com/nats-io/nats.go v1.27.0/go.mod h1:XpbWUlOElGwTYbMR7imivs7jJj9GtK7ypv321Wp6pjc=
|
github.com/nats-io/nats.go v1.28.0/go.mod h1:XpbWUlOElGwTYbMR7imivs7jJj9GtK7ypv321Wp6pjc=
|
||||||
github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA=
|
github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA=
|
||||||
github.com/nats-io/nkeys v0.4.4/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64=
|
github.com/nats-io/nkeys v0.4.4/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64=
|
||||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||||
|
|||||||
+2
-3
@@ -90,9 +90,8 @@ type advancedState struct {
|
|||||||
ii uint16 // position of last match, intended to overflow to reset.
|
ii uint16 // position of last match, intended to overflow to reset.
|
||||||
|
|
||||||
// input window: unprocessed data is window[index:windowEnd]
|
// input window: unprocessed data is window[index:windowEnd]
|
||||||
index int
|
index int
|
||||||
estBitsPerByte int
|
hashMatch [maxMatchLength + minMatchLength]uint32
|
||||||
hashMatch [maxMatchLength + minMatchLength]uint32
|
|
||||||
|
|
||||||
// Input hash chains
|
// Input hash chains
|
||||||
// hashHead[hashValue] contains the largest inputIndex with the specified hash value
|
// hashHead[hashValue] contains the largest inputIndex with the specified hash value
|
||||||
|
|||||||
-5
@@ -34,11 +34,6 @@ const (
|
|||||||
// Should preferably be a multiple of 6, since
|
// Should preferably be a multiple of 6, since
|
||||||
// we accumulate 6 bytes between writes to the buffer.
|
// we accumulate 6 bytes between writes to the buffer.
|
||||||
bufferFlushSize = 246
|
bufferFlushSize = 246
|
||||||
|
|
||||||
// bufferSize is the actual output byte buffer size.
|
|
||||||
// It must have additional headroom for a flush
|
|
||||||
// which can contain up to 8 bytes.
|
|
||||||
bufferSize = bufferFlushSize + 8
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Minimum length code that emits bits.
|
// Minimum length code that emits bits.
|
||||||
|
|||||||
-19
@@ -42,25 +42,6 @@ func quickSortByFreq(data []literalNode, a, b, maxDepth int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// siftDownByFreq implements the heap property on data[lo, hi).
|
|
||||||
// first is an offset into the array where the root of the heap lies.
|
|
||||||
func siftDownByFreq(data []literalNode, lo, hi, first int) {
|
|
||||||
root := lo
|
|
||||||
for {
|
|
||||||
child := 2*root + 1
|
|
||||||
if child >= hi {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if child+1 < hi && (data[first+child].freq == data[first+child+1].freq && data[first+child].literal < data[first+child+1].literal || data[first+child].freq < data[first+child+1].freq) {
|
|
||||||
child++
|
|
||||||
}
|
|
||||||
if data[first+root].freq == data[first+child].freq && data[first+root].literal > data[first+child].literal || data[first+root].freq > data[first+child].freq {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
data[first+root], data[first+child] = data[first+child], data[first+root]
|
|
||||||
root = child
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func doPivotByFreq(data []literalNode, lo, hi int) (midlo, midhi int) {
|
func doPivotByFreq(data []literalNode, lo, hi int) (midlo, midhi int) {
|
||||||
m := int(uint(lo+hi) >> 1) // Written like this to avoid integer overflow.
|
m := int(uint(lo+hi) >> 1) // Written like this to avoid integer overflow.
|
||||||
if hi-lo > 40 {
|
if hi-lo > 40 {
|
||||||
|
|||||||
-1
@@ -742,7 +742,6 @@ searchDict:
|
|||||||
x := load64(src, s-2)
|
x := load64(src, s-2)
|
||||||
m2Hash := hash6(x, tableBits)
|
m2Hash := hash6(x, tableBits)
|
||||||
currHash := hash6(x>>8, tableBits)
|
currHash := hash6(x>>8, tableBits)
|
||||||
candidate = int(table[currHash])
|
|
||||||
table[m2Hash] = uint32(s - 2)
|
table[m2Hash] = uint32(s - 2)
|
||||||
table[currHash] = uint32(s - 1)
|
table[currHash] = uint32(s - 1)
|
||||||
cv = load64(src, s)
|
cv = load64(src, s)
|
||||||
|
|||||||
+25
-19
@@ -157,7 +157,6 @@ func encodeBlockBetterGo(dst, src []byte) (d int) {
|
|||||||
index0 := base + 1
|
index0 := base + 1
|
||||||
index1 := s - 2
|
index1 := s - 2
|
||||||
|
|
||||||
cv = load64(src, s)
|
|
||||||
for index0 < index1 {
|
for index0 < index1 {
|
||||||
cv0 := load64(src, index0)
|
cv0 := load64(src, index0)
|
||||||
cv1 := load64(src, index1)
|
cv1 := load64(src, index1)
|
||||||
@@ -269,18 +268,21 @@ func encodeBlockBetterGo(dst, src []byte) (d int) {
|
|||||||
lTable[hash7(cv0, lTableBits)] = uint32(index0)
|
lTable[hash7(cv0, lTableBits)] = uint32(index0)
|
||||||
sTable[hash4(cv0>>8, sTableBits)] = uint32(index0 + 1)
|
sTable[hash4(cv0>>8, sTableBits)] = uint32(index0 + 1)
|
||||||
|
|
||||||
|
// lTable could be postponed, but very minor difference.
|
||||||
lTable[hash7(cv1, lTableBits)] = uint32(index1)
|
lTable[hash7(cv1, lTableBits)] = uint32(index1)
|
||||||
sTable[hash4(cv1>>8, sTableBits)] = uint32(index1 + 1)
|
sTable[hash4(cv1>>8, sTableBits)] = uint32(index1 + 1)
|
||||||
index0 += 1
|
index0 += 1
|
||||||
index1 -= 1
|
index1 -= 1
|
||||||
cv = load64(src, s)
|
cv = load64(src, s)
|
||||||
|
|
||||||
// index every second long in between.
|
// Index large values sparsely in between.
|
||||||
for index0 < index1 {
|
// We do two starting from different offsets for speed.
|
||||||
|
index2 := (index0 + index1 + 1) >> 1
|
||||||
|
for index2 < index1 {
|
||||||
lTable[hash7(load64(src, index0), lTableBits)] = uint32(index0)
|
lTable[hash7(load64(src, index0), lTableBits)] = uint32(index0)
|
||||||
lTable[hash7(load64(src, index1), lTableBits)] = uint32(index1)
|
lTable[hash7(load64(src, index2), lTableBits)] = uint32(index2)
|
||||||
index0 += 2
|
index0 += 2
|
||||||
index1 -= 2
|
index2 += 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,12 +461,14 @@ func encodeBlockBetterSnappyGo(dst, src []byte) (d int) {
|
|||||||
index1 -= 1
|
index1 -= 1
|
||||||
cv = load64(src, s)
|
cv = load64(src, s)
|
||||||
|
|
||||||
// index every second long in between.
|
// Index large values sparsely in between.
|
||||||
for index0 < index1 {
|
// We do two starting from different offsets for speed.
|
||||||
|
index2 := (index0 + index1 + 1) >> 1
|
||||||
|
for index2 < index1 {
|
||||||
lTable[hash7(load64(src, index0), lTableBits)] = uint32(index0)
|
lTable[hash7(load64(src, index0), lTableBits)] = uint32(index0)
|
||||||
lTable[hash7(load64(src, index1), lTableBits)] = uint32(index1)
|
lTable[hash7(load64(src, index2), lTableBits)] = uint32(index2)
|
||||||
index0 += 2
|
index0 += 2
|
||||||
index1 -= 2
|
index2 += 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -599,7 +603,6 @@ searchDict:
|
|||||||
if s >= sLimit {
|
if s >= sLimit {
|
||||||
break searchDict
|
break searchDict
|
||||||
}
|
}
|
||||||
cv = load64(src, s)
|
|
||||||
// Index in-between
|
// Index in-between
|
||||||
index0 := base + 1
|
index0 := base + 1
|
||||||
index1 := s - 2
|
index1 := s - 2
|
||||||
@@ -865,12 +868,14 @@ searchDict:
|
|||||||
index1 -= 1
|
index1 -= 1
|
||||||
cv = load64(src, s)
|
cv = load64(src, s)
|
||||||
|
|
||||||
// index every second long in between.
|
// Index large values sparsely in between.
|
||||||
for index0 < index1 {
|
// We do two starting from different offsets for speed.
|
||||||
|
index2 := (index0 + index1 + 1) >> 1
|
||||||
|
for index2 < index1 {
|
||||||
lTable[hash7(load64(src, index0), lTableBits)] = uint32(index0)
|
lTable[hash7(load64(src, index0), lTableBits)] = uint32(index0)
|
||||||
lTable[hash7(load64(src, index1), lTableBits)] = uint32(index1)
|
lTable[hash7(load64(src, index2), lTableBits)] = uint32(index2)
|
||||||
index0 += 2
|
index0 += 2
|
||||||
index1 -= 2
|
index2 += 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -961,7 +966,6 @@ searchDict:
|
|||||||
index0 := base + 1
|
index0 := base + 1
|
||||||
index1 := s - 2
|
index1 := s - 2
|
||||||
|
|
||||||
cv = load64(src, s)
|
|
||||||
for index0 < index1 {
|
for index0 < index1 {
|
||||||
cv0 := load64(src, index0)
|
cv0 := load64(src, index0)
|
||||||
cv1 := load64(src, index1)
|
cv1 := load64(src, index1)
|
||||||
@@ -1079,12 +1083,14 @@ searchDict:
|
|||||||
index1 -= 1
|
index1 -= 1
|
||||||
cv = load64(src, s)
|
cv = load64(src, s)
|
||||||
|
|
||||||
// index every second long in between.
|
// Index large values sparsely in between.
|
||||||
for index0 < index1 {
|
// We do two starting from different offsets for speed.
|
||||||
|
index2 := (index0 + index1 + 1) >> 1
|
||||||
|
for index2 < index1 {
|
||||||
lTable[hash7(load64(src, index0), lTableBits)] = uint32(index0)
|
lTable[hash7(load64(src, index0), lTableBits)] = uint32(index0)
|
||||||
lTable[hash7(load64(src, index1), lTableBits)] = uint32(index1)
|
lTable[hash7(load64(src, index2), lTableBits)] = uint32(index2)
|
||||||
index0 += 2
|
index0 += 2
|
||||||
index1 -= 2
|
index2 += 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+320
-335
File diff suppressed because it is too large
Load Diff
+7
@@ -147,6 +147,13 @@ type Reader struct {
|
|||||||
ignoreCRC bool
|
ignoreCRC bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetBufferCapacity returns the capacity of the internal buffer.
|
||||||
|
// This might be useful to know when reusing the same reader in combination
|
||||||
|
// with the lazy buffer option.
|
||||||
|
func (r *Reader) GetBufferCapacity() int {
|
||||||
|
return cap(r.buf)
|
||||||
|
}
|
||||||
|
|
||||||
// ensureBufferSize will ensure that the buffer can take at least n bytes.
|
// ensureBufferSize will ensure that the buffer can take at least n bytes.
|
||||||
// If false is returned the buffer exceeds maximum allowed size.
|
// If false is returned the buffer exceeds maximum allowed size.
|
||||||
func (r *Reader) ensureBufferSize(n int) bool {
|
func (r *Reader) ensureBufferSize(n int) bool {
|
||||||
|
|||||||
+1
-1
@@ -771,7 +771,7 @@ func (w *Writer) closeIndex(idx bool) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var index []byte
|
var index []byte
|
||||||
if w.err(nil) == nil && w.writer != nil {
|
if w.err(err) == nil && w.writer != nil {
|
||||||
// Create index.
|
// Create index.
|
||||||
if idx {
|
if idx {
|
||||||
compSize := int64(-1)
|
compSize := int64(-1)
|
||||||
|
|||||||
+3
-1
@@ -146,7 +146,9 @@ func parse(data, fp string, pedantic bool) (p *parser, err error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(p.mapping) == 0 {
|
||||||
|
return nil, fmt.Errorf("config has no values or is empty")
|
||||||
|
}
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+28
-7
@@ -38,13 +38,37 @@ type Logger struct {
|
|||||||
fl *fileLogger
|
fl *fileLogger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewStdLogger creates a logger with output directed to Stderr
|
type LogOption interface {
|
||||||
func NewStdLogger(time, debug, trace, colors, pid bool) *Logger {
|
isLoggerOption()
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogUTC controls whether timestamps in the log output should be UTC or local time.
|
||||||
|
type LogUTC bool
|
||||||
|
|
||||||
|
func (l LogUTC) isLoggerOption() {}
|
||||||
|
|
||||||
|
func logFlags(time bool, opts ...LogOption) int {
|
||||||
flags := 0
|
flags := 0
|
||||||
if time {
|
if time {
|
||||||
flags = log.LstdFlags | log.Lmicroseconds
|
flags = log.LstdFlags | log.Lmicroseconds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
switch v := opt.(type) {
|
||||||
|
case LogUTC:
|
||||||
|
if time && bool(v) {
|
||||||
|
flags |= log.LUTC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return flags
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStdLogger creates a logger with output directed to Stderr
|
||||||
|
func NewStdLogger(time, debug, trace, colors, pid bool, opts ...LogOption) *Logger {
|
||||||
|
flags := logFlags(time, opts...)
|
||||||
|
|
||||||
pre := ""
|
pre := ""
|
||||||
if pid {
|
if pid {
|
||||||
pre = pidPrefix()
|
pre = pidPrefix()
|
||||||
@@ -66,11 +90,8 @@ func NewStdLogger(time, debug, trace, colors, pid bool) *Logger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewFileLogger creates a logger with output directed to a file
|
// NewFileLogger creates a logger with output directed to a file
|
||||||
func NewFileLogger(filename string, time, debug, trace, pid bool) *Logger {
|
func NewFileLogger(filename string, time, debug, trace, pid bool, opts ...LogOption) *Logger {
|
||||||
flags := 0
|
flags := logFlags(time, opts...)
|
||||||
if time {
|
|
||||||
flags = log.LstdFlags | log.Lmicroseconds
|
|
||||||
}
|
|
||||||
|
|
||||||
pre := ""
|
pre := ""
|
||||||
if pid {
|
if pid {
|
||||||
|
|||||||
+297
@@ -0,0 +1,297 @@
|
|||||||
|
// Copyright 2023 The NATS Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package certidp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ocsp"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultAllowedClockSkew = 30 * time.Second
|
||||||
|
DefaultOCSPResponderTimeout = 2 * time.Second
|
||||||
|
DefaultTTLUnsetNextUpdate = 1 * time.Hour
|
||||||
|
)
|
||||||
|
|
||||||
|
type StatusAssertion int
|
||||||
|
|
||||||
|
var (
|
||||||
|
StatusAssertionStrToVal = map[string]StatusAssertion{
|
||||||
|
"good": ocsp.Good,
|
||||||
|
"revoked": ocsp.Revoked,
|
||||||
|
"unknown": ocsp.Unknown,
|
||||||
|
}
|
||||||
|
StatusAssertionValToStr = map[StatusAssertion]string{
|
||||||
|
ocsp.Good: "good",
|
||||||
|
ocsp.Revoked: "revoked",
|
||||||
|
ocsp.Unknown: "unknown",
|
||||||
|
}
|
||||||
|
StatusAssertionIntToVal = map[int]StatusAssertion{
|
||||||
|
0: ocsp.Good,
|
||||||
|
1: ocsp.Revoked,
|
||||||
|
2: ocsp.Unknown,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetStatusAssertionStr(sa int) string {
|
||||||
|
return StatusAssertionValToStr[StatusAssertionIntToVal[sa]]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sa StatusAssertion) MarshalJSON() ([]byte, error) {
|
||||||
|
str, ok := StatusAssertionValToStr[sa]
|
||||||
|
if !ok {
|
||||||
|
// set unknown as fallback
|
||||||
|
str = StatusAssertionValToStr[ocsp.Unknown]
|
||||||
|
}
|
||||||
|
return json.Marshal(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sa *StatusAssertion) UnmarshalJSON(in []byte) error {
|
||||||
|
v, ok := StatusAssertionStrToVal[strings.ReplaceAll(string(in), "\"", "")]
|
||||||
|
if !ok {
|
||||||
|
// set unknown as fallback
|
||||||
|
v = StatusAssertionStrToVal["unknown"]
|
||||||
|
}
|
||||||
|
*sa = v
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChainLink struct {
|
||||||
|
Leaf *x509.Certificate
|
||||||
|
Issuer *x509.Certificate
|
||||||
|
OCSPWebEndpoints *[]*url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
// OCSPPeerConfig holds the parsed OCSP peer configuration section of TLS configuration
|
||||||
|
type OCSPPeerConfig struct {
|
||||||
|
Verify bool
|
||||||
|
Timeout float64
|
||||||
|
ClockSkew float64
|
||||||
|
WarnOnly bool
|
||||||
|
UnknownIsGood bool
|
||||||
|
AllowWhenCAUnreachable bool
|
||||||
|
TTLUnsetNextUpdate float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOCSPPeerConfig() *OCSPPeerConfig {
|
||||||
|
return &OCSPPeerConfig{
|
||||||
|
Verify: false,
|
||||||
|
Timeout: DefaultOCSPResponderTimeout.Seconds(),
|
||||||
|
ClockSkew: DefaultAllowedClockSkew.Seconds(),
|
||||||
|
WarnOnly: false,
|
||||||
|
UnknownIsGood: false,
|
||||||
|
AllowWhenCAUnreachable: false,
|
||||||
|
TTLUnsetNextUpdate: DefaultTTLUnsetNextUpdate.Seconds(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log is a neutral method of passing server loggers to plugins
|
||||||
|
type Log struct {
|
||||||
|
Debugf func(format string, v ...interface{})
|
||||||
|
Noticef func(format string, v ...interface{})
|
||||||
|
Warnf func(format string, v ...interface{})
|
||||||
|
Errorf func(format string, v ...interface{})
|
||||||
|
Tracef func(format string, v ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type CertInfo struct {
|
||||||
|
Subject string `json:"subject,omitempty"`
|
||||||
|
Issuer string `json:"issuer,omitempty"`
|
||||||
|
Fingerprint string `json:"fingerprint,omitempty"`
|
||||||
|
Raw []byte `json:"raw,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var OCSPPeerUsage = `
|
||||||
|
For client, leaf spoke (remotes), and leaf hub connections, you may enable OCSP peer validation:
|
||||||
|
|
||||||
|
tls {
|
||||||
|
...
|
||||||
|
# mTLS must be enabled (with exception of Leaf remotes)
|
||||||
|
verify: true
|
||||||
|
...
|
||||||
|
# short form enables peer verify and takes option defaults
|
||||||
|
ocsp_peer: true
|
||||||
|
|
||||||
|
# long form includes settable options
|
||||||
|
ocsp_peer {
|
||||||
|
# Enable OCSP peer validation (default false)
|
||||||
|
verify: true
|
||||||
|
|
||||||
|
# OCSP responder timeout in seconds (may be fractional, default 2 seconds)
|
||||||
|
ca_timeout: 2
|
||||||
|
|
||||||
|
# Allowed skew between server and OCSP responder time in seconds (may be fractional, default 30 seconds)
|
||||||
|
allowed_clockskew: 30
|
||||||
|
|
||||||
|
# Warn-only and never reject connections (default false)
|
||||||
|
warn_only: false
|
||||||
|
|
||||||
|
# Treat response Unknown status as valid certificate (default false)
|
||||||
|
unknown_is_good: false
|
||||||
|
|
||||||
|
# Warn-only if no CA response can be obtained and no cached revocation exists (default false)
|
||||||
|
allow_when_ca_unreachable: false
|
||||||
|
|
||||||
|
# If response NextUpdate unset by CA, set a default cache TTL in seconds from ThisUpdate (default 1 hour)
|
||||||
|
cache_ttl_when_next_update_unset: 3600
|
||||||
|
}
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
Note: OCSP validation for route and gateway connections is enabled using the 'ocsp' configuration option.
|
||||||
|
`
|
||||||
|
|
||||||
|
// GenerateFingerprint returns a base64-encoded SHA256 hash of the raw certificate
|
||||||
|
func GenerateFingerprint(cert *x509.Certificate) string {
|
||||||
|
data := sha256.Sum256(cert.Raw)
|
||||||
|
return base64.StdEncoding.EncodeToString(data[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func getWebEndpoints(uris []string) []*url.URL {
|
||||||
|
var urls []*url.URL
|
||||||
|
for _, uri := range uris {
|
||||||
|
endpoint, err := url.ParseRequestURI(uri)
|
||||||
|
if err != nil {
|
||||||
|
// skip invalid URLs
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if endpoint.Scheme != "http" && endpoint.Scheme != "https" {
|
||||||
|
// skip non-web URLs
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
urls = append(urls, endpoint)
|
||||||
|
}
|
||||||
|
return urls
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSubjectDNForm returns RDN sequence concatenation of the certificate's subject to be
|
||||||
|
// used in logs, events, etc. Should never be used for reliable cache matching or other crypto purposes.
|
||||||
|
func GetSubjectDNForm(cert *x509.Certificate) string {
|
||||||
|
if cert == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strings.TrimSuffix(fmt.Sprintf("%s+", cert.Subject.ToRDNSequence()), "+")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIssuerDNForm returns RDN sequence concatenation of the certificate's issuer to be
|
||||||
|
// used in logs, events, etc. Should never be used for reliable cache matching or other crypto purposes.
|
||||||
|
func GetIssuerDNForm(cert *x509.Certificate) string {
|
||||||
|
if cert == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strings.TrimSuffix(fmt.Sprintf("%s+", cert.Issuer.ToRDNSequence()), "+")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CertOCSPEligible checks if the certificate's issuer has populated AIA with OCSP responder endpoint(s)
|
||||||
|
// and is thus eligible for OCSP validation
|
||||||
|
func CertOCSPEligible(link *ChainLink) bool {
|
||||||
|
if link == nil || link.Leaf.Raw == nil || len(link.Leaf.Raw) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if link.Leaf.OCSPServer == nil || len(link.Leaf.OCSPServer) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
urls := getWebEndpoints(link.Leaf.OCSPServer)
|
||||||
|
if len(urls) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
link.OCSPWebEndpoints = &urls
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLeafIssuerCert returns the issuer certificate of the leaf (positional) certificate in the chain
|
||||||
|
func GetLeafIssuerCert(chain []*x509.Certificate, leafPos int) *x509.Certificate {
|
||||||
|
if len(chain) == 0 || leafPos < 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// self-signed certificate or too-big leafPos
|
||||||
|
if leafPos >= len(chain)-1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// returns pointer to issuer cert or nil
|
||||||
|
return (chain)[leafPos+1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// OCSPResponseCurrent checks if the OCSP response is current (i.e. not expired and not future effective)
|
||||||
|
func OCSPResponseCurrent(ocspr *ocsp.Response, opts *OCSPPeerConfig, log *Log) bool {
|
||||||
|
skew := time.Duration(opts.ClockSkew * float64(time.Second))
|
||||||
|
if skew < 0*time.Second {
|
||||||
|
skew = DefaultAllowedClockSkew
|
||||||
|
}
|
||||||
|
now := time.Now().UTC()
|
||||||
|
// Typical effectivity check based on CA response ThisUpdate and NextUpdate semantics
|
||||||
|
if !ocspr.NextUpdate.IsZero() && ocspr.NextUpdate.Before(now.Add(-1*skew)) {
|
||||||
|
t := ocspr.NextUpdate.Format(time.RFC3339Nano)
|
||||||
|
nt := now.Format(time.RFC3339Nano)
|
||||||
|
log.Debugf(DbgResponseExpired, t, nt, skew)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// CA responder can assert NextUpdate unset, in which case use config option to set a default cache TTL
|
||||||
|
if ocspr.NextUpdate.IsZero() {
|
||||||
|
ttl := time.Duration(opts.TTLUnsetNextUpdate * float64(time.Second))
|
||||||
|
if ttl < 0*time.Second {
|
||||||
|
ttl = DefaultTTLUnsetNextUpdate
|
||||||
|
}
|
||||||
|
expiryTime := ocspr.ThisUpdate.Add(ttl)
|
||||||
|
if expiryTime.Before(now.Add(-1 * skew)) {
|
||||||
|
t := expiryTime.Format(time.RFC3339Nano)
|
||||||
|
nt := now.Format(time.RFC3339Nano)
|
||||||
|
log.Debugf(DbgResponseTTLExpired, t, nt, skew)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ocspr.ThisUpdate.After(now.Add(skew)) {
|
||||||
|
t := ocspr.ThisUpdate.Format(time.RFC3339Nano)
|
||||||
|
nt := now.Format(time.RFC3339Nano)
|
||||||
|
log.Debugf(DbgResponseFutureDated, t, nt, skew)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidDelegationCheck checks if the CA OCSP Response was signed by a valid CA Issuer delegate as per (RFC 6960, section 4.2.2.2)
|
||||||
|
// If a valid delegate or direct-signed by CA Issuer, true returned.
|
||||||
|
func ValidDelegationCheck(iss *x509.Certificate, ocspr *ocsp.Response) bool {
|
||||||
|
// This call assumes prior successful parse and signature validation of the OCSP response
|
||||||
|
// The Go OCSP library (as of x/crypto/ocsp v0.9) will detect and perform a 1-level delegate signature check but does not
|
||||||
|
// implement the additional criteria for delegation specified in RFC 6960, section 4.2.2.2.
|
||||||
|
if iss == nil || ocspr == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// not a delegation, no-op
|
||||||
|
if ocspr.Certificate == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// delegate is self-same with CA Issuer, not a delegation although response issued in that form
|
||||||
|
if ocspr.Certificate.Equal(iss) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// we need to verify CA Issuer stamped id-kp-OCSPSigning on delegate
|
||||||
|
delegatedSigner := false
|
||||||
|
for _, keyUseExt := range ocspr.Certificate.ExtKeyUsage {
|
||||||
|
if keyUseExt == x509.ExtKeyUsageOCSPSigning {
|
||||||
|
delegatedSigner = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return delegatedSigner
|
||||||
|
}
|
||||||
+106
@@ -0,0 +1,106 @@
|
|||||||
|
// Copyright 2023 The NATS Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package certidp
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Returned errors
|
||||||
|
ErrIllegalPeerOptsConfig = "expected map to define OCSP peer options, got [%T]"
|
||||||
|
ErrIllegalCacheOptsConfig = "expected map to define OCSP peer cache options, got [%T]"
|
||||||
|
ErrParsingPeerOptFieldGeneric = "error parsing tls peer config, unknown field [%q]"
|
||||||
|
ErrParsingPeerOptFieldTypeConversion = "error parsing tls peer config, conversion error: %s"
|
||||||
|
ErrParsingCacheOptFieldTypeConversion = "error parsing OCSP peer cache config, conversion error: %s"
|
||||||
|
ErrUnableToPlugTLSEmptyConfig = "unable to plug TLS verify connection, config is nil"
|
||||||
|
ErrMTLSRequired = "OCSP peer verification for client connections requires TLS verify (mTLS) to be enabled"
|
||||||
|
ErrUnableToPlugTLSClient = "unable to register client OCSP verification"
|
||||||
|
ErrUnableToPlugTLSServer = "unable to register server OCSP verification"
|
||||||
|
ErrCannotWriteCompressed = "error writing to compression writer: %w"
|
||||||
|
ErrCannotReadCompressed = "error reading compression reader: %w"
|
||||||
|
ErrTruncatedWrite = "short write on body (%d != %d)"
|
||||||
|
ErrCannotCloseWriter = "error closing compression writer: %w"
|
||||||
|
ErrParsingCacheOptFieldGeneric = "error parsing OCSP peer cache config, unknown field [%q]"
|
||||||
|
ErrUnknownCacheType = "error parsing OCSP peer cache config, unknown type [%s]"
|
||||||
|
ErrInvalidChainlink = "invalid chain link"
|
||||||
|
ErrBadResponderHTTPStatus = "bad OCSP responder http status: [%d]"
|
||||||
|
ErrNoAvailOCSPServers = "no available OCSP servers"
|
||||||
|
ErrFailedWithAllRequests = "exhausted OCSP responders: %w"
|
||||||
|
|
||||||
|
// Direct logged errors
|
||||||
|
ErrLoadCacheFail = "Unable to load OCSP peer cache: %s"
|
||||||
|
ErrSaveCacheFail = "Unable to save OCSP peer cache: %s"
|
||||||
|
ErrBadCacheTypeConfig = "Unimplemented OCSP peer cache type [%v]"
|
||||||
|
ErrResponseCompressFail = "Unable to compress OCSP response for key [%s]: %s"
|
||||||
|
ErrResponseDecompressFail = "Unable to decompress OCSP response for key [%s]: %s"
|
||||||
|
ErrPeerEmptyNoEvent = "Peer certificate is nil, cannot send OCSP peer reject event"
|
||||||
|
ErrPeerEmptyAutoReject = "Peer certificate is nil, rejecting OCSP peer"
|
||||||
|
|
||||||
|
// Debug information
|
||||||
|
DbgPlugTLSForKind = "Plugging TLS OCSP peer for [%s]"
|
||||||
|
DbgNumServerChains = "Peer OCSP enabled: %d TLS server chain(s) will be evaluated"
|
||||||
|
DbgNumClientChains = "Peer OCSP enabled: %d TLS client chain(s) will be evaluated"
|
||||||
|
DbgLinksInChain = "Chain [%d]: %d total link(s)"
|
||||||
|
DbgSelfSignedValid = "Chain [%d] is self-signed, thus peer is valid"
|
||||||
|
DbgValidNonOCSPChain = "Chain [%d] has no OCSP eligible links, thus peer is valid"
|
||||||
|
DbgChainIsOCSPEligible = "Chain [%d] has %d OCSP eligible link(s)"
|
||||||
|
DbgChainIsOCSPValid = "Chain [%d] is OCSP valid for all eligible links, thus peer is valid"
|
||||||
|
DbgNoOCSPValidChains = "No OCSP valid chains, thus peer is invalid"
|
||||||
|
DbgCheckingCacheForCert = "Checking OCSP peer cache for [%s], key [%s]"
|
||||||
|
DbgCurrentResponseCached = "Cached OCSP response is current, status [%s]"
|
||||||
|
DbgExpiredResponseCached = "Cached OCSP response is expired, status [%s]"
|
||||||
|
DbgOCSPValidPeerLink = "OCSP verify pass for [%s]"
|
||||||
|
DbgCachingResponse = "Caching OCSP response for [%s], key [%s]"
|
||||||
|
DbgAchievedCompression = "OCSP response compression ratio: [%f]"
|
||||||
|
DbgCacheHit = "OCSP peer cache hit for key [%s]"
|
||||||
|
DbgCacheMiss = "OCSP peer cache miss for key [%s]"
|
||||||
|
DbgPreservedRevocation = "Revoked OCSP response for key [%s] preserved by cache policy"
|
||||||
|
DbgDeletingCacheResponse = "Deleting OCSP peer cached response for key [%s]"
|
||||||
|
DbgStartingCache = "Starting OCSP peer cache"
|
||||||
|
DbgStoppingCache = "Stopping OCSP peer cache"
|
||||||
|
DbgLoadingCache = "Loading OCSP peer cache [%s]"
|
||||||
|
DbgNoCacheFound = "No OCSP peer cache found, starting with empty cache"
|
||||||
|
DbgSavingCache = "Saving OCSP peer cache [%s]"
|
||||||
|
DbgCacheSaved = "Saved OCSP peer cache successfully (%d bytes)"
|
||||||
|
DbgMakingCARequest = "Trying OCSP responder url [%s]"
|
||||||
|
DbgResponseExpired = "OCSP response NextUpdate [%s] is before now [%s] with clockskew [%s]"
|
||||||
|
DbgResponseTTLExpired = "OCSP response cache expiry [%s] is before now [%s] with clockskew [%s]"
|
||||||
|
DbgResponseFutureDated = "OCSP response ThisUpdate [%s] is before now [%s] with clockskew [%s]"
|
||||||
|
DbgCacheSaveTimerExpired = "OCSP peer cache save timer expired"
|
||||||
|
DbgCacheDirtySave = "OCSP peer cache is dirty, saving"
|
||||||
|
|
||||||
|
// Returned to peer as TLS reject reason
|
||||||
|
MsgTLSClientRejectConnection = "client not OCSP valid"
|
||||||
|
MsgTLSServerRejectConnection = "server not OCSP valid"
|
||||||
|
|
||||||
|
// Expected runtime errors (direct logged)
|
||||||
|
ErrCAResponderCalloutFail = "Attempt to obtain OCSP response from CA responder for [%s] failed: %s"
|
||||||
|
ErrNewCAResponseNotCurrent = "New OCSP CA response obtained for [%s] but not current"
|
||||||
|
ErrCAResponseParseFailed = "Could not parse OCSP CA response for [%s]: %s"
|
||||||
|
ErrOCSPInvalidPeerLink = "OCSP verify fail for [%s] with CA status [%s]"
|
||||||
|
|
||||||
|
// Policy override warnings (direct logged)
|
||||||
|
MsgAllowWhenCAUnreachableOccurred = "Failed to obtain OCSP CA response for [%s] but AllowWhenCAUnreachable set; no cached revocation so allowing"
|
||||||
|
MsgAllowWhenCAUnreachableOccurredCachedRevoke = "Failed to obtain OCSP CA response for [%s] but AllowWhenCAUnreachable set; cached revocation exists so rejecting"
|
||||||
|
MsgAllowWarnOnlyOccurred = "OCSP verify fail for [%s] but WarnOnly is true so allowing"
|
||||||
|
|
||||||
|
// Info (direct logged)
|
||||||
|
MsgCacheOnline = "OCSP peer cache online, type [%s]"
|
||||||
|
MsgCacheOffline = "OCSP peer cache offline, type [%s]"
|
||||||
|
|
||||||
|
// OCSP cert invalid reasons (debug and event reasons)
|
||||||
|
MsgFailedOCSPResponseFetch = "Failed OCSP response fetch"
|
||||||
|
MsgOCSPResponseNotEffective = "OCSP response not in effectivity window"
|
||||||
|
MsgFailedOCSPResponseParse = "Failed OCSP response parse"
|
||||||
|
MsgOCSPResponseInvalidStatus = "Invalid OCSP response status: %s"
|
||||||
|
MsgOCSPResponseDelegationInvalid = "Invalid OCSP response delegation: %s"
|
||||||
|
MsgCachedOCSPResponseInvalid = "Invalid cached OCSP response for [%s] with fingerprint [%s]"
|
||||||
|
)
|
||||||
+83
@@ -0,0 +1,83 @@
|
|||||||
|
// Copyright 2023 The NATS Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package certidp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ocsp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func FetchOCSPResponse(link *ChainLink, opts *OCSPPeerConfig, log *Log) ([]byte, error) {
|
||||||
|
if link == nil || link.Leaf == nil || link.Issuer == nil || opts == nil || log == nil {
|
||||||
|
return nil, fmt.Errorf(ErrInvalidChainlink)
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout := time.Duration(opts.Timeout * float64(time.Second))
|
||||||
|
if timeout <= 0*time.Second {
|
||||||
|
timeout = DefaultOCSPResponderTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
getRequestBytes := func(u string, hc *http.Client) ([]byte, error) {
|
||||||
|
resp, err := hc.Get(u)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf(ErrBadResponderHTTPStatus, resp.StatusCode)
|
||||||
|
}
|
||||||
|
return io.ReadAll(resp.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request documentation:
|
||||||
|
// https://tools.ietf.org/html/rfc6960#appendix-A.1
|
||||||
|
|
||||||
|
reqDER, err := ocsp.CreateRequest(link.Leaf, link.Issuer, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reqEnc := base64.StdEncoding.EncodeToString(reqDER)
|
||||||
|
|
||||||
|
responders := *link.OCSPWebEndpoints
|
||||||
|
|
||||||
|
if len(responders) == 0 {
|
||||||
|
return nil, fmt.Errorf(ErrNoAvailOCSPServers)
|
||||||
|
}
|
||||||
|
|
||||||
|
var raw []byte
|
||||||
|
hc := &http.Client{
|
||||||
|
Timeout: timeout,
|
||||||
|
}
|
||||||
|
for _, u := range responders {
|
||||||
|
url := u.String()
|
||||||
|
log.Debugf(DbgMakingCARequest, url)
|
||||||
|
url = strings.TrimSuffix(url, "/")
|
||||||
|
raw, err = getRequestBytes(fmt.Sprintf("%s/%s", url, reqEnc), hc)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(ErrFailedWithAllRequests, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return raw, nil
|
||||||
|
}
|
||||||
+102
@@ -0,0 +1,102 @@
|
|||||||
|
// Copyright 2022-2023 The NATS Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package certstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/x509"
|
||||||
|
"io"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StoreType int
|
||||||
|
|
||||||
|
const MATCHBYEMPTY = 0
|
||||||
|
const STOREEMPTY = 0
|
||||||
|
|
||||||
|
const (
|
||||||
|
windowsCurrentUser StoreType = iota + 1
|
||||||
|
windowsLocalMachine
|
||||||
|
)
|
||||||
|
|
||||||
|
var StoreMap = map[string]StoreType{
|
||||||
|
"windowscurrentuser": windowsCurrentUser,
|
||||||
|
"windowslocalmachine": windowsLocalMachine,
|
||||||
|
}
|
||||||
|
|
||||||
|
var StoreOSMap = map[StoreType]string{
|
||||||
|
windowsCurrentUser: "windows",
|
||||||
|
windowsLocalMachine: "windows",
|
||||||
|
}
|
||||||
|
|
||||||
|
type MatchByType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
matchByIssuer MatchByType = iota + 1
|
||||||
|
matchBySubject
|
||||||
|
)
|
||||||
|
|
||||||
|
var MatchByMap = map[string]MatchByType{
|
||||||
|
"issuer": matchByIssuer,
|
||||||
|
"subject": matchBySubject,
|
||||||
|
}
|
||||||
|
|
||||||
|
var Usage = `
|
||||||
|
In place of cert_file and key_file you may use the windows certificate store:
|
||||||
|
|
||||||
|
tls {
|
||||||
|
cert_store: "WindowsCurrentUser"
|
||||||
|
cert_match_by: "Subject"
|
||||||
|
cert_match: "MyServer123"
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
func ParseCertStore(certStore string) (StoreType, error) {
|
||||||
|
certStoreType, exists := StoreMap[strings.ToLower(certStore)]
|
||||||
|
if !exists {
|
||||||
|
return 0, ErrBadCertStore
|
||||||
|
}
|
||||||
|
validOS, exists := StoreOSMap[certStoreType]
|
||||||
|
if !exists || validOS != runtime.GOOS {
|
||||||
|
return 0, ErrOSNotCompatCertStore
|
||||||
|
}
|
||||||
|
return certStoreType, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseCertMatchBy(certMatchBy string) (MatchByType, error) {
|
||||||
|
certMatchByType, exists := MatchByMap[strings.ToLower(certMatchBy)]
|
||||||
|
if !exists {
|
||||||
|
return 0, ErrBadMatchByType
|
||||||
|
}
|
||||||
|
return certMatchByType, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLeafIssuer(leaf *x509.Certificate, vOpts x509.VerifyOptions) (issuer *x509.Certificate) {
|
||||||
|
chains, err := leaf.Verify(vOpts)
|
||||||
|
if err != nil || len(chains) == 0 {
|
||||||
|
issuer = nil
|
||||||
|
} else {
|
||||||
|
issuer = chains[0][1]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// credential provides access to a public key and is a crypto.Signer.
|
||||||
|
type credential interface {
|
||||||
|
// Public returns the public key corresponding to the leaf certificate.
|
||||||
|
Public() crypto.PublicKey
|
||||||
|
// Sign signs digest with the private key.
|
||||||
|
Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error)
|
||||||
|
}
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
// Copyright 2022-2023 The NATS Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package certstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/tls"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = MATCHBYEMPTY
|
||||||
|
|
||||||
|
// otherKey implements crypto.Signer and crypto.Decrypter to satisfy linter on platforms that don't implement certstore
|
||||||
|
type otherKey struct{}
|
||||||
|
|
||||||
|
func TLSConfig(certStore StoreType, certMatchBy MatchByType, certMatch string, config *tls.Config) error {
|
||||||
|
_, _, _, _ = certStore, certMatchBy, certMatch, config
|
||||||
|
return ErrOSNotCompatCertStore
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public always returns nil public key since this is a stub on non-supported platform
|
||||||
|
func (k otherKey) Public() crypto.PublicKey {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign always returns a nil signature since this is a stub on non-supported platform
|
||||||
|
func (k otherKey) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) {
|
||||||
|
_, _, _ = rand, digest, opts
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify interface conformance.
|
||||||
|
var _ credential = &otherKey{}
|
||||||
+827
@@ -0,0 +1,827 @@
|
|||||||
|
// Copyright 2022-2023 The NATS Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
// Adapted, updated, and enhanced from CertToStore, https://github.com/google/certtostore/releases/tag/v1.0.2
|
||||||
|
// Apache License, Version 2.0, Copyright 2017 Google Inc.
|
||||||
|
|
||||||
|
package certstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math/big"
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"unicode/utf16"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/cryptobyte"
|
||||||
|
"golang.org/x/crypto/cryptobyte/asn1"
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// wincrypt.h constants
|
||||||
|
winAcquireCached = 0x1 // CRYPT_ACQUIRE_CACHE_FLAG
|
||||||
|
winAcquireSilent = 0x40 // CRYPT_ACQUIRE_SILENT_FLAG
|
||||||
|
winAcquireOnlyNCryptKey = 0x40000 // CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG
|
||||||
|
winEncodingX509ASN = 1 // X509_ASN_ENCODING
|
||||||
|
winEncodingPKCS7 = 65536 // PKCS_7_ASN_ENCODING
|
||||||
|
winCertStoreProvSystem = 10 // CERT_STORE_PROV_SYSTEM
|
||||||
|
winCertStoreCurrentUser = uint32(winCertStoreCurrentUserID << winCompareShift) // CERT_SYSTEM_STORE_CURRENT_USER
|
||||||
|
winCertStoreLocalMachine = uint32(winCertStoreLocalMachineID << winCompareShift) // CERT_SYSTEM_STORE_LOCAL_MACHINE
|
||||||
|
winCertStoreCurrentUserID = 1 // CERT_SYSTEM_STORE_CURRENT_USER_ID
|
||||||
|
winCertStoreLocalMachineID = 2 // CERT_SYSTEM_STORE_LOCAL_MACHINE_ID
|
||||||
|
winInfoIssuerFlag = 4 // CERT_INFO_ISSUER_FLAG
|
||||||
|
winInfoSubjectFlag = 7 // CERT_INFO_SUBJECT_FLAG
|
||||||
|
winCompareNameStrW = 8 // CERT_COMPARE_NAME_STR_A
|
||||||
|
winCompareShift = 16 // CERT_COMPARE_SHIFT
|
||||||
|
|
||||||
|
// Reference https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certfindcertificateinstore
|
||||||
|
winFindIssuerStr = winCompareNameStrW<<winCompareShift | winInfoIssuerFlag // CERT_FIND_ISSUER_STR_W
|
||||||
|
winFindSubjectStr = winCompareNameStrW<<winCompareShift | winInfoSubjectFlag // CERT_FIND_SUBJECT_STR_W
|
||||||
|
|
||||||
|
winNcryptKeySpec = 0xFFFFFFFF // CERT_NCRYPT_KEY_SPEC
|
||||||
|
|
||||||
|
winBCryptPadPKCS1 uintptr = 0x2
|
||||||
|
winBCryptPadPSS uintptr = 0x8 // Modern TLS 1.2+
|
||||||
|
winBCryptPadPSSSalt uint32 = 32 // default 20, 32 optimal for typical SHA256 hash
|
||||||
|
|
||||||
|
winRSA1Magic = 0x31415352 // "RSA1" BCRYPT_RSAPUBLIC_MAGIC
|
||||||
|
|
||||||
|
winECS1Magic = 0x31534345 // "ECS1" BCRYPT_ECDSA_PUBLIC_P256_MAGIC
|
||||||
|
winECS3Magic = 0x33534345 // "ECS3" BCRYPT_ECDSA_PUBLIC_P384_MAGIC
|
||||||
|
winECS5Magic = 0x35534345 // "ECS5" BCRYPT_ECDSA_PUBLIC_P521_MAGIC
|
||||||
|
|
||||||
|
winECK1Magic = 0x314B4345 // "ECK1" BCRYPT_ECDH_PUBLIC_P256_MAGIC
|
||||||
|
winECK3Magic = 0x334B4345 // "ECK3" BCRYPT_ECDH_PUBLIC_P384_MAGIC
|
||||||
|
winECK5Magic = 0x354B4345 // "ECK5" BCRYPT_ECDH_PUBLIC_P521_MAGIC
|
||||||
|
|
||||||
|
winCryptENotFound = 0x80092004 // CRYPT_E_NOT_FOUND
|
||||||
|
|
||||||
|
providerMSSoftware = "Microsoft Software Key Storage Provider"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
winBCryptRSAPublicBlob = winWide("RSAPUBLICBLOB")
|
||||||
|
winBCryptECCPublicBlob = winWide("ECCPUBLICBLOB")
|
||||||
|
|
||||||
|
winNCryptAlgorithmGroupProperty = winWide("Algorithm Group") // NCRYPT_ALGORITHM_GROUP_PROPERTY
|
||||||
|
winNCryptUniqueNameProperty = winWide("Unique Name") // NCRYPT_UNIQUE_NAME_PROPERTY
|
||||||
|
winNCryptECCCurveNameProperty = winWide("ECCCurveName") // NCRYPT_ECC_CURVE_NAME_PROPERTY
|
||||||
|
|
||||||
|
winCurveIDs = map[uint32]elliptic.Curve{
|
||||||
|
winECS1Magic: elliptic.P256(), // BCRYPT_ECDSA_PUBLIC_P256_MAGIC
|
||||||
|
winECS3Magic: elliptic.P384(), // BCRYPT_ECDSA_PUBLIC_P384_MAGIC
|
||||||
|
winECS5Magic: elliptic.P521(), // BCRYPT_ECDSA_PUBLIC_P521_MAGIC
|
||||||
|
winECK1Magic: elliptic.P256(), // BCRYPT_ECDH_PUBLIC_P256_MAGIC
|
||||||
|
winECK3Magic: elliptic.P384(), // BCRYPT_ECDH_PUBLIC_P384_MAGIC
|
||||||
|
winECK5Magic: elliptic.P521(), // BCRYPT_ECDH_PUBLIC_P521_MAGIC
|
||||||
|
}
|
||||||
|
|
||||||
|
winCurveNames = map[string]elliptic.Curve{
|
||||||
|
"nistP256": elliptic.P256(), // BCRYPT_ECC_CURVE_NISTP256
|
||||||
|
"nistP384": elliptic.P384(), // BCRYPT_ECC_CURVE_NISTP384
|
||||||
|
"nistP521": elliptic.P521(), // BCRYPT_ECC_CURVE_NISTP521
|
||||||
|
}
|
||||||
|
|
||||||
|
winAlgIDs = map[crypto.Hash]*uint16{
|
||||||
|
crypto.SHA1: winWide("SHA1"), // BCRYPT_SHA1_ALGORITHM
|
||||||
|
crypto.SHA256: winWide("SHA256"), // BCRYPT_SHA256_ALGORITHM
|
||||||
|
crypto.SHA384: winWide("SHA384"), // BCRYPT_SHA384_ALGORITHM
|
||||||
|
crypto.SHA512: winWide("SHA512"), // BCRYPT_SHA512_ALGORITHM
|
||||||
|
}
|
||||||
|
|
||||||
|
// MY is well-known system store on Windows that holds personal certificates
|
||||||
|
winMyStore = winWide("MY")
|
||||||
|
|
||||||
|
// These DLLs must be available on all Windows hosts
|
||||||
|
winCrypt32 = windows.MustLoadDLL("crypt32.dll")
|
||||||
|
winNCrypt = windows.MustLoadDLL("ncrypt.dll")
|
||||||
|
|
||||||
|
winCertFindCertificateInStore = winCrypt32.MustFindProc("CertFindCertificateInStore")
|
||||||
|
winCryptAcquireCertificatePrivateKey = winCrypt32.MustFindProc("CryptAcquireCertificatePrivateKey")
|
||||||
|
winNCryptExportKey = winNCrypt.MustFindProc("NCryptExportKey")
|
||||||
|
winNCryptOpenStorageProvider = winNCrypt.MustFindProc("NCryptOpenStorageProvider")
|
||||||
|
winNCryptGetProperty = winNCrypt.MustFindProc("NCryptGetProperty")
|
||||||
|
winNCryptSignHash = winNCrypt.MustFindProc("NCryptSignHash")
|
||||||
|
|
||||||
|
winFnGetProperty = winGetProperty
|
||||||
|
)
|
||||||
|
|
||||||
|
type winPKCS1PaddingInfo struct {
|
||||||
|
pszAlgID *uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
type winPSSPaddingInfo struct {
|
||||||
|
pszAlgID *uint16
|
||||||
|
cbSalt uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLSConfig fulfills the same function as reading cert and key pair from pem files but
|
||||||
|
// sources the Windows certificate store instead
|
||||||
|
func TLSConfig(certStore StoreType, certMatchBy MatchByType, certMatch string, config *tls.Config) error {
|
||||||
|
var (
|
||||||
|
leaf *x509.Certificate
|
||||||
|
leafCtx *windows.CertContext
|
||||||
|
pk *winKey
|
||||||
|
vOpts = x509.VerifyOptions{}
|
||||||
|
chains [][]*x509.Certificate
|
||||||
|
chain []*x509.Certificate
|
||||||
|
rawChain [][]byte
|
||||||
|
)
|
||||||
|
|
||||||
|
// By StoreType, open a store
|
||||||
|
if certStore == windowsCurrentUser || certStore == windowsLocalMachine {
|
||||||
|
var scope uint32
|
||||||
|
cs, err := winOpenCertStore(providerMSSoftware)
|
||||||
|
if err != nil || cs == nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if certStore == windowsCurrentUser {
|
||||||
|
scope = winCertStoreCurrentUser
|
||||||
|
}
|
||||||
|
if certStore == windowsLocalMachine {
|
||||||
|
scope = winCertStoreLocalMachine
|
||||||
|
}
|
||||||
|
|
||||||
|
// certByIssuer or certBySubject
|
||||||
|
if certMatchBy == matchBySubject || certMatchBy == MATCHBYEMPTY {
|
||||||
|
leaf, leafCtx, err = cs.certBySubject(certMatch, scope)
|
||||||
|
} else if certMatchBy == matchByIssuer {
|
||||||
|
leaf, leafCtx, err = cs.certByIssuer(certMatch, scope)
|
||||||
|
} else {
|
||||||
|
return ErrBadMatchByType
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
// pass through error from cert search
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if leaf == nil || leafCtx == nil {
|
||||||
|
return ErrFailedCertSearch
|
||||||
|
}
|
||||||
|
pk, err = cs.certKey(leafCtx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if pk == nil {
|
||||||
|
return ErrNoPrivateKeyStoreRef
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return ErrBadCertStore
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get intermediates in the cert store for the found leaf IFF there is a full chain of trust in the store
|
||||||
|
// otherwise just use leaf as the final chain.
|
||||||
|
//
|
||||||
|
// Using std lib Verify as a reliable way to get valid chains out of the win store for the leaf; however,
|
||||||
|
// using empty options since server TLS stanza could be TLS role as server identity or client identity.
|
||||||
|
chains, err := leaf.Verify(vOpts)
|
||||||
|
if err != nil || len(chains) == 0 {
|
||||||
|
chains = append(chains, []*x509.Certificate{leaf})
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have at least one verified chain so pop the first chain and remove the self-signed CA cert (if present)
|
||||||
|
// from the end of the chain
|
||||||
|
chain = chains[0]
|
||||||
|
if len(chain) > 1 {
|
||||||
|
chain = chain[:len(chain)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// For tls.Certificate.Certificate need a [][]byte from []*x509.Certificate
|
||||||
|
// Approximate capacity for efficiency
|
||||||
|
rawChain = make([][]byte, 0, len(chain))
|
||||||
|
for _, link := range chain {
|
||||||
|
rawChain = append(rawChain, link.Raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsCert := tls.Certificate{
|
||||||
|
Certificate: rawChain,
|
||||||
|
PrivateKey: pk,
|
||||||
|
Leaf: leaf,
|
||||||
|
}
|
||||||
|
config.Certificates = []tls.Certificate{tlsCert}
|
||||||
|
|
||||||
|
// note: pk is a windows pointer (not freed by Go) but needs to live the life of the server for Signing.
|
||||||
|
// The cert context (leafCtx) windows pointer must not be freed underneath the pk so also life of the server.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// winWide returns a pointer to uint16 representing the equivalent
|
||||||
|
// to a Windows LPCWSTR.
|
||||||
|
func winWide(s string) *uint16 {
|
||||||
|
w := utf16.Encode([]rune(s))
|
||||||
|
w = append(w, 0)
|
||||||
|
return &w[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// winOpenProvider gets a provider handle for subsequent calls
|
||||||
|
func winOpenProvider(provider string) (uintptr, error) {
|
||||||
|
var hProv uintptr
|
||||||
|
pname := winWide(provider)
|
||||||
|
// Open the provider, the last parameter is not used
|
||||||
|
r, _, err := winNCryptOpenStorageProvider.Call(uintptr(unsafe.Pointer(&hProv)), uintptr(unsafe.Pointer(pname)), 0)
|
||||||
|
if r == 0 {
|
||||||
|
return hProv, nil
|
||||||
|
}
|
||||||
|
return hProv, fmt.Errorf("NCryptOpenStorageProvider returned %X: %v", r, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// winFindCert wraps the CertFindCertificateInStore library call. Note that any cert context passed
|
||||||
|
// into prev will be freed. If no certificate was found, nil will be returned.
|
||||||
|
func winFindCert(store windows.Handle, enc, findFlags, findType uint32, para *uint16, prev *windows.CertContext) (*windows.CertContext, error) {
|
||||||
|
h, _, err := winCertFindCertificateInStore.Call(
|
||||||
|
uintptr(store),
|
||||||
|
uintptr(enc),
|
||||||
|
uintptr(findFlags),
|
||||||
|
uintptr(findType),
|
||||||
|
uintptr(unsafe.Pointer(para)),
|
||||||
|
uintptr(unsafe.Pointer(prev)),
|
||||||
|
)
|
||||||
|
if h == 0 {
|
||||||
|
// Actual error, or simply not found?
|
||||||
|
if errno, ok := err.(syscall.Errno); ok && errno == winCryptENotFound {
|
||||||
|
return nil, ErrFailedCertSearch
|
||||||
|
}
|
||||||
|
return nil, ErrFailedCertSearch
|
||||||
|
}
|
||||||
|
// nolint:govet
|
||||||
|
return (*windows.CertContext)(unsafe.Pointer(h)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// winCertStore is a store implementation for the Windows Certificate Store
|
||||||
|
type winCertStore struct {
|
||||||
|
Prov uintptr
|
||||||
|
ProvName string
|
||||||
|
stores map[string]*winStoreHandle
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// winOpenCertStore creates a winCertStore
|
||||||
|
func winOpenCertStore(provider string) (*winCertStore, error) {
|
||||||
|
cngProv, err := winOpenProvider(provider)
|
||||||
|
if err != nil {
|
||||||
|
// pass through error from winOpenProvider
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
wcs := &winCertStore{
|
||||||
|
Prov: cngProv,
|
||||||
|
ProvName: provider,
|
||||||
|
stores: make(map[string]*winStoreHandle),
|
||||||
|
}
|
||||||
|
|
||||||
|
return wcs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// winCertContextToX509 creates an x509.Certificate from a Windows cert context.
|
||||||
|
func winCertContextToX509(ctx *windows.CertContext) (*x509.Certificate, error) {
|
||||||
|
var der []byte
|
||||||
|
slice := (*reflect.SliceHeader)(unsafe.Pointer(&der))
|
||||||
|
slice.Data = uintptr(unsafe.Pointer(ctx.EncodedCert))
|
||||||
|
slice.Len = int(ctx.Length)
|
||||||
|
slice.Cap = int(ctx.Length)
|
||||||
|
return x509.ParseCertificate(der)
|
||||||
|
}
|
||||||
|
|
||||||
|
// certByIssuer matches and returns the first certificate found by passed issuer.
|
||||||
|
// CertContext pointer returned allows subsequent key operations like Sign. Caller specifies
|
||||||
|
// current user's personal certs or local machine's personal certs using storeType.
|
||||||
|
// See CERT_FIND_ISSUER_STR description at https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certfindcertificateinstore
|
||||||
|
func (w *winCertStore) certByIssuer(issuer string, storeType uint32) (*x509.Certificate, *windows.CertContext, error) {
|
||||||
|
return w.certSearch(winFindIssuerStr, issuer, winMyStore, storeType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// certBySubject matches and returns the first certificate found by passed subject field.
|
||||||
|
// CertContext pointer returned allows subsequent key operations like Sign. Caller specifies
|
||||||
|
// current user's personal certs or local machine's personal certs using storeType.
|
||||||
|
// See CERT_FIND_SUBJECT_STR description at https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certfindcertificateinstore
|
||||||
|
func (w *winCertStore) certBySubject(subject string, storeType uint32) (*x509.Certificate, *windows.CertContext, error) {
|
||||||
|
return w.certSearch(winFindSubjectStr, subject, winMyStore, storeType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// certSearch is a helper function to lookup certificates based on search type and match value.
|
||||||
|
// store is used to specify which store to perform the lookup in (system or user).
|
||||||
|
func (w *winCertStore) certSearch(searchType uint32, matchValue string, searchRoot *uint16, store uint32) (*x509.Certificate, *windows.CertContext, error) {
|
||||||
|
// store handle to "MY" store
|
||||||
|
h, err := w.storeHandle(store, searchRoot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var prev *windows.CertContext
|
||||||
|
var cert *x509.Certificate
|
||||||
|
|
||||||
|
i, err := windows.UTF16PtrFromString(matchValue)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, ErrFailedCertSearch
|
||||||
|
}
|
||||||
|
|
||||||
|
// pass 0 as the third parameter because it is not used
|
||||||
|
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa376064(v=vs.85).aspx
|
||||||
|
nc, err := winFindCert(h, winEncodingX509ASN|winEncodingPKCS7, 0, searchType, i, prev)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if nc != nil {
|
||||||
|
// certificate found
|
||||||
|
prev = nc
|
||||||
|
|
||||||
|
// Extract the DER-encoded certificate from the cert context
|
||||||
|
xc, err := winCertContextToX509(nc)
|
||||||
|
if err == nil {
|
||||||
|
cert = xc
|
||||||
|
} else {
|
||||||
|
return nil, nil, ErrFailedX509Extract
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, nil, ErrFailedCertSearch
|
||||||
|
}
|
||||||
|
|
||||||
|
if cert == nil {
|
||||||
|
return nil, nil, ErrFailedX509Extract
|
||||||
|
}
|
||||||
|
|
||||||
|
return cert, prev, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type winStoreHandle struct {
|
||||||
|
handle *windows.Handle
|
||||||
|
}
|
||||||
|
|
||||||
|
func winNewStoreHandle(provider uint32, store *uint16) (*winStoreHandle, error) {
|
||||||
|
var s winStoreHandle
|
||||||
|
if s.handle != nil {
|
||||||
|
return &s, nil
|
||||||
|
}
|
||||||
|
st, err := windows.CertOpenStore(
|
||||||
|
winCertStoreProvSystem,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
provider,
|
||||||
|
uintptr(unsafe.Pointer(store)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrBadCryptoStoreProvider
|
||||||
|
}
|
||||||
|
s.handle = &st
|
||||||
|
return &s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// winKey implements crypto.Signer and crypto.Decrypter for key based operations.
|
||||||
|
type winKey struct {
|
||||||
|
handle uintptr
|
||||||
|
pub crypto.PublicKey
|
||||||
|
Container string
|
||||||
|
AlgorithmGroup string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public exports a public key to implement crypto.Signer
|
||||||
|
func (k winKey) Public() crypto.PublicKey {
|
||||||
|
return k.pub
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign returns the signature of a hash to implement crypto.Signer
|
||||||
|
func (k winKey) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
|
||||||
|
switch k.AlgorithmGroup {
|
||||||
|
case "ECDSA", "ECDH":
|
||||||
|
return winSignECDSA(k.handle, digest)
|
||||||
|
case "RSA":
|
||||||
|
hf := opts.HashFunc()
|
||||||
|
algID, ok := winAlgIDs[hf]
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrBadRSAHashAlgorithm
|
||||||
|
}
|
||||||
|
switch opts.(type) {
|
||||||
|
case *rsa.PSSOptions:
|
||||||
|
return winSignRSAPSSPadding(k.handle, digest, algID)
|
||||||
|
default:
|
||||||
|
return winSignRSAPKCS1Padding(k.handle, digest, algID)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, ErrBadSigningAlgorithm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func winSignECDSA(kh uintptr, digest []byte) ([]byte, error) {
|
||||||
|
var size uint32
|
||||||
|
// Obtain the size of the signature
|
||||||
|
r, _, _ := winNCryptSignHash.Call(
|
||||||
|
kh,
|
||||||
|
0,
|
||||||
|
uintptr(unsafe.Pointer(&digest[0])),
|
||||||
|
uintptr(len(digest)),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
uintptr(unsafe.Pointer(&size)),
|
||||||
|
0)
|
||||||
|
if r != 0 {
|
||||||
|
return nil, ErrStoreECDSASigningError
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtain the signature data
|
||||||
|
buf := make([]byte, size)
|
||||||
|
r, _, _ = winNCryptSignHash.Call(
|
||||||
|
kh,
|
||||||
|
0,
|
||||||
|
uintptr(unsafe.Pointer(&digest[0])),
|
||||||
|
uintptr(len(digest)),
|
||||||
|
uintptr(unsafe.Pointer(&buf[0])),
|
||||||
|
uintptr(size),
|
||||||
|
uintptr(unsafe.Pointer(&size)),
|
||||||
|
0)
|
||||||
|
if r != 0 {
|
||||||
|
return nil, ErrStoreECDSASigningError
|
||||||
|
}
|
||||||
|
if len(buf) != int(size) {
|
||||||
|
return nil, ErrStoreECDSASigningError
|
||||||
|
}
|
||||||
|
|
||||||
|
return winPackECDSASigValue(bytes.NewReader(buf[:size]), len(digest))
|
||||||
|
}
|
||||||
|
|
||||||
|
func winPackECDSASigValue(r io.Reader, digestLength int) ([]byte, error) {
|
||||||
|
sigR := make([]byte, digestLength)
|
||||||
|
if _, err := io.ReadFull(r, sigR); err != nil {
|
||||||
|
return nil, ErrStoreECDSASigningError
|
||||||
|
}
|
||||||
|
|
||||||
|
sigS := make([]byte, digestLength)
|
||||||
|
if _, err := io.ReadFull(r, sigS); err != nil {
|
||||||
|
return nil, ErrStoreECDSASigningError
|
||||||
|
}
|
||||||
|
|
||||||
|
var b cryptobyte.Builder
|
||||||
|
b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {
|
||||||
|
b.AddASN1BigInt(new(big.Int).SetBytes(sigR))
|
||||||
|
b.AddASN1BigInt(new(big.Int).SetBytes(sigS))
|
||||||
|
})
|
||||||
|
return b.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func winSignRSAPKCS1Padding(kh uintptr, digest []byte, algID *uint16) ([]byte, error) {
|
||||||
|
// PKCS#1 v1.5 padding for some TLS 1.2
|
||||||
|
padInfo := winPKCS1PaddingInfo{pszAlgID: algID}
|
||||||
|
var size uint32
|
||||||
|
// Obtain the size of the signature
|
||||||
|
r, _, _ := winNCryptSignHash.Call(
|
||||||
|
kh,
|
||||||
|
uintptr(unsafe.Pointer(&padInfo)),
|
||||||
|
uintptr(unsafe.Pointer(&digest[0])),
|
||||||
|
uintptr(len(digest)),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
uintptr(unsafe.Pointer(&size)),
|
||||||
|
winBCryptPadPKCS1)
|
||||||
|
if r != 0 {
|
||||||
|
return nil, ErrStoreRSASigningError
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtain the signature data
|
||||||
|
sig := make([]byte, size)
|
||||||
|
r, _, _ = winNCryptSignHash.Call(
|
||||||
|
kh,
|
||||||
|
uintptr(unsafe.Pointer(&padInfo)),
|
||||||
|
uintptr(unsafe.Pointer(&digest[0])),
|
||||||
|
uintptr(len(digest)),
|
||||||
|
uintptr(unsafe.Pointer(&sig[0])),
|
||||||
|
uintptr(size),
|
||||||
|
uintptr(unsafe.Pointer(&size)),
|
||||||
|
winBCryptPadPKCS1)
|
||||||
|
if r != 0 {
|
||||||
|
return nil, ErrStoreRSASigningError
|
||||||
|
}
|
||||||
|
|
||||||
|
return sig[:size], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func winSignRSAPSSPadding(kh uintptr, digest []byte, algID *uint16) ([]byte, error) {
|
||||||
|
// PSS padding for TLS 1.3 and some TLS 1.2
|
||||||
|
padInfo := winPSSPaddingInfo{pszAlgID: algID, cbSalt: winBCryptPadPSSSalt}
|
||||||
|
|
||||||
|
var size uint32
|
||||||
|
// Obtain the size of the signature
|
||||||
|
r, _, _ := winNCryptSignHash.Call(
|
||||||
|
kh,
|
||||||
|
uintptr(unsafe.Pointer(&padInfo)),
|
||||||
|
uintptr(unsafe.Pointer(&digest[0])),
|
||||||
|
uintptr(len(digest)),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
uintptr(unsafe.Pointer(&size)),
|
||||||
|
winBCryptPadPSS)
|
||||||
|
if r != 0 {
|
||||||
|
return nil, ErrStoreRSASigningError
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtain the signature data
|
||||||
|
sig := make([]byte, size)
|
||||||
|
r, _, _ = winNCryptSignHash.Call(
|
||||||
|
kh,
|
||||||
|
uintptr(unsafe.Pointer(&padInfo)),
|
||||||
|
uintptr(unsafe.Pointer(&digest[0])),
|
||||||
|
uintptr(len(digest)),
|
||||||
|
uintptr(unsafe.Pointer(&sig[0])),
|
||||||
|
uintptr(size),
|
||||||
|
uintptr(unsafe.Pointer(&size)),
|
||||||
|
winBCryptPadPSS)
|
||||||
|
if r != 0 {
|
||||||
|
return nil, ErrStoreRSASigningError
|
||||||
|
}
|
||||||
|
|
||||||
|
return sig[:size], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// certKey wraps CryptAcquireCertificatePrivateKey. It obtains the CNG private
|
||||||
|
// key of a known certificate and returns a pointer to a winKey which implements
|
||||||
|
// both crypto.Signer. When a nil cert context is passed
|
||||||
|
// a nil key is intentionally returned, to model the expected behavior of a
|
||||||
|
// non-existent cert having no private key.
|
||||||
|
// https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptacquirecertificateprivatekey
|
||||||
|
func (w *winCertStore) certKey(cert *windows.CertContext) (*winKey, error) {
|
||||||
|
// Return early if a nil cert was passed.
|
||||||
|
if cert == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
kh uintptr
|
||||||
|
spec uint32
|
||||||
|
mustFree int
|
||||||
|
)
|
||||||
|
r, _, _ := winCryptAcquireCertificatePrivateKey.Call(
|
||||||
|
uintptr(unsafe.Pointer(cert)),
|
||||||
|
winAcquireCached|winAcquireSilent|winAcquireOnlyNCryptKey,
|
||||||
|
0, // Reserved, must be null.
|
||||||
|
uintptr(unsafe.Pointer(&kh)),
|
||||||
|
uintptr(unsafe.Pointer(&spec)),
|
||||||
|
uintptr(unsafe.Pointer(&mustFree)),
|
||||||
|
)
|
||||||
|
// If the function succeeds, the return value is nonzero (TRUE).
|
||||||
|
if r == 0 {
|
||||||
|
return nil, ErrNoPrivateKeyStoreRef
|
||||||
|
}
|
||||||
|
if mustFree != 0 {
|
||||||
|
return nil, ErrNoPrivateKeyStoreRef
|
||||||
|
}
|
||||||
|
if spec != winNcryptKeySpec {
|
||||||
|
return nil, ErrNoPrivateKeyStoreRef
|
||||||
|
}
|
||||||
|
|
||||||
|
return winKeyMetadata(kh)
|
||||||
|
}
|
||||||
|
|
||||||
|
func winKeyMetadata(kh uintptr) (*winKey, error) {
|
||||||
|
// uc is used to populate the unique container name attribute of the private key
|
||||||
|
uc, err := winGetPropertyStr(kh, winNCryptUniqueNameProperty)
|
||||||
|
if err != nil {
|
||||||
|
// unable to determine key unique name
|
||||||
|
return nil, ErrExtractingPrivateKeyMetadata
|
||||||
|
}
|
||||||
|
|
||||||
|
alg, err := winGetPropertyStr(kh, winNCryptAlgorithmGroupProperty)
|
||||||
|
if err != nil {
|
||||||
|
// unable to determine key algorithm
|
||||||
|
return nil, ErrExtractingPrivateKeyMetadata
|
||||||
|
}
|
||||||
|
|
||||||
|
var pub crypto.PublicKey
|
||||||
|
|
||||||
|
switch alg {
|
||||||
|
case "ECDSA", "ECDH":
|
||||||
|
buf, err := winExport(kh, winBCryptECCPublicBlob)
|
||||||
|
if err != nil {
|
||||||
|
// failed to export ECC public key
|
||||||
|
return nil, ErrExtractingECCPublicKey
|
||||||
|
}
|
||||||
|
pub, err = unmarshalECC(buf, kh)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrExtractingECCPublicKey
|
||||||
|
}
|
||||||
|
case "RSA":
|
||||||
|
buf, err := winExport(kh, winBCryptRSAPublicBlob)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrExtractingRSAPublicKey
|
||||||
|
}
|
||||||
|
pub, err = winUnmarshalRSA(buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrExtractingRSAPublicKey
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, ErrBadPublicKeyAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
return &winKey{handle: kh, pub: pub, Container: uc, AlgorithmGroup: alg}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func winGetProperty(kh uintptr, property *uint16) ([]byte, error) {
|
||||||
|
var strSize uint32
|
||||||
|
r, _, _ := winNCryptGetProperty.Call(
|
||||||
|
kh,
|
||||||
|
uintptr(unsafe.Pointer(property)),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
uintptr(unsafe.Pointer(&strSize)),
|
||||||
|
0,
|
||||||
|
0)
|
||||||
|
if r != 0 {
|
||||||
|
return nil, ErrExtractPropertyFromKey
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, strSize)
|
||||||
|
r, _, _ = winNCryptGetProperty.Call(
|
||||||
|
kh,
|
||||||
|
uintptr(unsafe.Pointer(property)),
|
||||||
|
uintptr(unsafe.Pointer(&buf[0])),
|
||||||
|
uintptr(strSize),
|
||||||
|
uintptr(unsafe.Pointer(&strSize)),
|
||||||
|
0,
|
||||||
|
0)
|
||||||
|
if r != 0 {
|
||||||
|
return nil, ErrExtractPropertyFromKey
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func winGetPropertyStr(kh uintptr, property *uint16) (string, error) {
|
||||||
|
buf, err := winFnGetProperty(kh, property)
|
||||||
|
if err != nil {
|
||||||
|
return "", ErrExtractPropertyFromKey
|
||||||
|
}
|
||||||
|
uc := bytes.ReplaceAll(buf, []byte{0x00}, []byte(""))
|
||||||
|
return string(uc), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func winExport(kh uintptr, blobType *uint16) ([]byte, error) {
|
||||||
|
var size uint32
|
||||||
|
// When obtaining the size of a public key, most parameters are not required
|
||||||
|
r, _, _ := winNCryptExportKey.Call(
|
||||||
|
kh,
|
||||||
|
0,
|
||||||
|
uintptr(unsafe.Pointer(blobType)),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
uintptr(unsafe.Pointer(&size)),
|
||||||
|
0)
|
||||||
|
if r != 0 {
|
||||||
|
return nil, ErrExtractingPublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// Place the exported key in buf now that we know the size required
|
||||||
|
buf := make([]byte, size)
|
||||||
|
r, _, _ = winNCryptExportKey.Call(
|
||||||
|
kh,
|
||||||
|
0,
|
||||||
|
uintptr(unsafe.Pointer(blobType)),
|
||||||
|
0,
|
||||||
|
uintptr(unsafe.Pointer(&buf[0])),
|
||||||
|
uintptr(size),
|
||||||
|
uintptr(unsafe.Pointer(&size)),
|
||||||
|
0)
|
||||||
|
if r != 0 {
|
||||||
|
return nil, ErrExtractingPublicKey
|
||||||
|
}
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalECC(buf []byte, kh uintptr) (*ecdsa.PublicKey, error) {
|
||||||
|
// BCRYPT_ECCKEY_BLOB from bcrypt.h
|
||||||
|
header := struct {
|
||||||
|
Magic uint32
|
||||||
|
Key uint32
|
||||||
|
}{}
|
||||||
|
|
||||||
|
r := bytes.NewReader(buf)
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &header); err != nil {
|
||||||
|
return nil, ErrExtractingECCPublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
curve, ok := winCurveIDs[header.Magic]
|
||||||
|
if !ok {
|
||||||
|
// Fix for b/185945636, where despite specifying the curve, nCrypt returns
|
||||||
|
// an incorrect response with BCRYPT_ECDSA_PUBLIC_GENERIC_MAGIC.
|
||||||
|
var err error
|
||||||
|
curve, err = winCurveName(kh)
|
||||||
|
if err != nil {
|
||||||
|
// unsupported header magic or cannot match the curve by name
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keyX := make([]byte, header.Key)
|
||||||
|
if n, err := r.Read(keyX); n != int(header.Key) || err != nil {
|
||||||
|
// failed to read key X
|
||||||
|
return nil, ErrExtractingECCPublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
keyY := make([]byte, header.Key)
|
||||||
|
if n, err := r.Read(keyY); n != int(header.Key) || err != nil {
|
||||||
|
// failed to read key Y
|
||||||
|
return nil, ErrExtractingECCPublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
pub := &ecdsa.PublicKey{
|
||||||
|
Curve: curve,
|
||||||
|
X: new(big.Int).SetBytes(keyX),
|
||||||
|
Y: new(big.Int).SetBytes(keyY),
|
||||||
|
}
|
||||||
|
return pub, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// winCurveName reads the curve name property and returns the corresponding curve.
|
||||||
|
func winCurveName(kh uintptr) (elliptic.Curve, error) {
|
||||||
|
cn, err := winGetPropertyStr(kh, winNCryptECCCurveNameProperty)
|
||||||
|
if err != nil {
|
||||||
|
// unable to determine the curve property name
|
||||||
|
return nil, ErrExtractPropertyFromKey
|
||||||
|
}
|
||||||
|
curve, ok := winCurveNames[cn]
|
||||||
|
if !ok {
|
||||||
|
// unknown curve name
|
||||||
|
return nil, ErrBadECCCurveName
|
||||||
|
}
|
||||||
|
return curve, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func winUnmarshalRSA(buf []byte) (*rsa.PublicKey, error) {
|
||||||
|
// BCRYPT_RSA_BLOB from bcrypt.h
|
||||||
|
header := struct {
|
||||||
|
Magic uint32
|
||||||
|
BitLength uint32
|
||||||
|
PublicExpSize uint32
|
||||||
|
ModulusSize uint32
|
||||||
|
UnusedPrime1 uint32
|
||||||
|
UnusedPrime2 uint32
|
||||||
|
}{}
|
||||||
|
|
||||||
|
r := bytes.NewReader(buf)
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &header); err != nil {
|
||||||
|
return nil, ErrExtractingRSAPublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
if header.Magic != winRSA1Magic {
|
||||||
|
// invalid header magic
|
||||||
|
return nil, ErrExtractingRSAPublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
if header.PublicExpSize > 8 {
|
||||||
|
// unsupported public exponent size
|
||||||
|
return nil, ErrExtractingRSAPublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
exp := make([]byte, 8)
|
||||||
|
if n, err := r.Read(exp[8-header.PublicExpSize:]); n != int(header.PublicExpSize) || err != nil {
|
||||||
|
// failed to read public exponent
|
||||||
|
return nil, ErrExtractingRSAPublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
mod := make([]byte, header.ModulusSize)
|
||||||
|
if n, err := r.Read(mod); n != int(header.ModulusSize) || err != nil {
|
||||||
|
// failed to read modulus
|
||||||
|
return nil, ErrExtractingRSAPublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
pub := &rsa.PublicKey{
|
||||||
|
N: new(big.Int).SetBytes(mod),
|
||||||
|
E: int(binary.BigEndian.Uint64(exp)),
|
||||||
|
}
|
||||||
|
return pub, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// storeHandle returns a handle to a given cert store, opening the handle as needed.
|
||||||
|
func (w *winCertStore) storeHandle(provider uint32, store *uint16) (windows.Handle, error) {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
key := fmt.Sprintf("%d%s", provider, windows.UTF16PtrToString(store))
|
||||||
|
var err error
|
||||||
|
if w.stores[key] == nil {
|
||||||
|
w.stores[key], err = winNewStoreHandle(provider, store)
|
||||||
|
if err != nil {
|
||||||
|
return 0, ErrBadCryptoStoreProvider
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return *w.stores[key].handle, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify interface conformance.
|
||||||
|
var _ credential = &winKey{}
|
||||||
+73
@@ -0,0 +1,73 @@
|
|||||||
|
package certstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrBadCryptoStoreProvider represents inablity to establish link with a certificate store
|
||||||
|
ErrBadCryptoStoreProvider = errors.New("unable to open certificate store or store not available")
|
||||||
|
|
||||||
|
// ErrBadRSAHashAlgorithm represents a bad or unsupported RSA hash algorithm
|
||||||
|
ErrBadRSAHashAlgorithm = errors.New("unsupported RSA hash algorithm")
|
||||||
|
|
||||||
|
// ErrBadSigningAlgorithm represents a bad or unsupported signing algorithm
|
||||||
|
ErrBadSigningAlgorithm = errors.New("unsupported signing algorithm")
|
||||||
|
|
||||||
|
// ErrStoreRSASigningError represents an error returned from store during RSA signature
|
||||||
|
ErrStoreRSASigningError = errors.New("unable to obtain RSA signature from store")
|
||||||
|
|
||||||
|
// ErrStoreECDSASigningError represents an error returned from store during ECDSA signature
|
||||||
|
ErrStoreECDSASigningError = errors.New("unable to obtain ECDSA signature from store")
|
||||||
|
|
||||||
|
// ErrNoPrivateKeyStoreRef represents an error getting a handle to a private key in store
|
||||||
|
ErrNoPrivateKeyStoreRef = errors.New("unable to obtain private key handle from store")
|
||||||
|
|
||||||
|
// ErrExtractingPrivateKeyMetadata represents a family of errors extracting metadata about the private key in store
|
||||||
|
ErrExtractingPrivateKeyMetadata = errors.New("unable to extract private key metadata")
|
||||||
|
|
||||||
|
// ErrExtractingECCPublicKey represents an error exporting ECC-type public key from store
|
||||||
|
ErrExtractingECCPublicKey = errors.New("unable to extract ECC public key from store")
|
||||||
|
|
||||||
|
// ErrExtractingRSAPublicKey represents an error exporting RSA-type public key from store
|
||||||
|
ErrExtractingRSAPublicKey = errors.New("unable to extract RSA public key from store")
|
||||||
|
|
||||||
|
// ErrExtractingPublicKey represents a general error exporting public key from store
|
||||||
|
ErrExtractingPublicKey = errors.New("unable to extract public key from store")
|
||||||
|
|
||||||
|
// ErrBadPublicKeyAlgorithm represents a bad or unsupported public key algorithm
|
||||||
|
ErrBadPublicKeyAlgorithm = errors.New("unsupported public key algorithm")
|
||||||
|
|
||||||
|
// ErrExtractPropertyFromKey represents a general failure to extract a metadata property field
|
||||||
|
ErrExtractPropertyFromKey = errors.New("unable to extract property from key")
|
||||||
|
|
||||||
|
// ErrBadECCCurveName represents an ECC signature curve name that is bad or unsupported
|
||||||
|
ErrBadECCCurveName = errors.New("unsupported ECC curve name")
|
||||||
|
|
||||||
|
// ErrFailedCertSearch represents not able to find certificate in store
|
||||||
|
ErrFailedCertSearch = errors.New("unable to find certificate in store")
|
||||||
|
|
||||||
|
// ErrFailedX509Extract represents not being able to extract x509 certificate from found cert in store
|
||||||
|
ErrFailedX509Extract = errors.New("unable to extract x509 from certificate")
|
||||||
|
|
||||||
|
// ErrBadMatchByType represents unknown CERT_MATCH_BY passed
|
||||||
|
ErrBadMatchByType = errors.New("cert match by type not implemented")
|
||||||
|
|
||||||
|
// ErrBadCertStore represents unknown CERT_STORE passed
|
||||||
|
ErrBadCertStore = errors.New("cert store type not implemented")
|
||||||
|
|
||||||
|
// ErrConflictCertFileAndStore represents ambiguous configuration of both file and store
|
||||||
|
ErrConflictCertFileAndStore = errors.New("'cert_file' and 'cert_store' may not both be configured")
|
||||||
|
|
||||||
|
// ErrBadCertStoreField represents malformed cert_store option
|
||||||
|
ErrBadCertStoreField = errors.New("expected 'cert_store' to be a valid non-empty string")
|
||||||
|
|
||||||
|
// ErrBadCertMatchByField represents malformed cert_match_by option
|
||||||
|
ErrBadCertMatchByField = errors.New("expected 'cert_match_by' to be a valid non-empty string")
|
||||||
|
|
||||||
|
// ErrBadCertMatchField represents malformed cert_match option
|
||||||
|
ErrBadCertMatchField = errors.New("expected 'cert_match' to be a valid non-empty string")
|
||||||
|
|
||||||
|
// ErrOSNotCompatCertStore represents cert_store passed that exists but is not valid on current OS
|
||||||
|
ErrOSNotCompatCertStore = errors.New("cert_store not compatible with current operating system")
|
||||||
|
)
|
||||||
+44
-40
@@ -789,15 +789,16 @@ func (c *client) subsAtLimit() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func minLimit(value *int32, limit int32) bool {
|
func minLimit(value *int32, limit int32) bool {
|
||||||
if *value != jwt.NoLimit {
|
v := atomic.LoadInt32(value)
|
||||||
|
if v != jwt.NoLimit {
|
||||||
if limit != jwt.NoLimit {
|
if limit != jwt.NoLimit {
|
||||||
if limit < *value {
|
if limit < v {
|
||||||
*value = limit
|
atomic.StoreInt32(value, limit)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if limit != jwt.NoLimit {
|
} else if limit != jwt.NoLimit {
|
||||||
*value = limit
|
atomic.StoreInt32(value, limit)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -810,7 +811,7 @@ func (c *client) applyAccountLimits() {
|
|||||||
if c.acc == nil || (c.kind != CLIENT && c.kind != LEAF) {
|
if c.acc == nil || (c.kind != CLIENT && c.kind != LEAF) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.mpay = jwt.NoLimit
|
atomic.StoreInt32(&c.mpay, jwt.NoLimit)
|
||||||
c.msubs = jwt.NoLimit
|
c.msubs = jwt.NoLimit
|
||||||
if c.opts.JWT != _EMPTY_ { // user jwt implies account
|
if c.opts.JWT != _EMPTY_ { // user jwt implies account
|
||||||
if uc, _ := jwt.DecodeUserClaims(c.opts.JWT); uc != nil {
|
if uc, _ := jwt.DecodeUserClaims(c.opts.JWT); uc != nil {
|
||||||
@@ -2170,7 +2171,7 @@ func (c *client) generateClientInfoJSON(info Info) []byte {
|
|||||||
if c.srv != nil { // Otherwise lame duck info can panic
|
if c.srv != nil { // Otherwise lame duck info can panic
|
||||||
c.srv.websocket.mu.RLock()
|
c.srv.websocket.mu.RLock()
|
||||||
info.TLSAvailable = c.srv.websocket.tls
|
info.TLSAvailable = c.srv.websocket.tls
|
||||||
if c.srv.websocket.server != nil {
|
if c.srv.websocket.tls && c.srv.websocket.server != nil {
|
||||||
if tc := c.srv.websocket.server.TLSConfig; tc != nil {
|
if tc := c.srv.websocket.server.TLSConfig; tc != nil {
|
||||||
info.TLSRequired = !tc.InsecureSkipVerify
|
info.TLSRequired = !tc.InsecureSkipVerify
|
||||||
}
|
}
|
||||||
@@ -3127,20 +3128,14 @@ var needFlush = struct{}{}
|
|||||||
// deliverMsg will deliver a message to a matching subscription and its underlying client.
|
// deliverMsg will deliver a message to a matching subscription and its underlying client.
|
||||||
// We process all connection/client types. mh is the part that will be protocol/client specific.
|
// We process all connection/client types. mh is the part that will be protocol/client specific.
|
||||||
func (c *client) deliverMsg(prodIsMQTT bool, sub *subscription, acc *Account, subject, reply, mh, msg []byte, gwrply bool) bool {
|
func (c *client) deliverMsg(prodIsMQTT bool, sub *subscription, acc *Account, subject, reply, mh, msg []byte, gwrply bool) bool {
|
||||||
// Check sub client and check echo
|
// Check sub client and check echo. Only do this if not a service import.
|
||||||
if sub.client == nil || c == sub.client && !sub.client.echo {
|
if sub.client == nil || (c == sub.client && !sub.client.echo && !sub.si) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
client := sub.client
|
client := sub.client
|
||||||
client.mu.Lock()
|
client.mu.Lock()
|
||||||
|
|
||||||
// Check echo
|
|
||||||
if c == client && !client.echo {
|
|
||||||
client.mu.Unlock()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we have a subscribe deny clause. This will trigger us to check the subject
|
// Check if we have a subscribe deny clause. This will trigger us to check the subject
|
||||||
// for a match against the denied subjects.
|
// for a match against the denied subjects.
|
||||||
if client.mperms != nil && client.checkDenySub(string(subject)) {
|
if client.mperms != nil && client.checkDenySub(string(subject)) {
|
||||||
@@ -3582,15 +3577,21 @@ func (c *client) processInboundClientMsg(msg []byte) (bool, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Mostly under testing scenarios.
|
// Mostly under testing scenarios.
|
||||||
|
c.mu.Lock()
|
||||||
if c.srv == nil || c.acc == nil {
|
if c.srv == nil || c.acc == nil {
|
||||||
|
c.mu.Unlock()
|
||||||
return false, false
|
return false, false
|
||||||
}
|
}
|
||||||
|
acc := c.acc
|
||||||
|
genidAddr := &acc.sl.genid
|
||||||
|
|
||||||
// Check pub permissions
|
// Check pub permissions
|
||||||
if c.perms != nil && (c.perms.pub.allow != nil || c.perms.pub.deny != nil) && !c.pubAllowed(string(c.pa.subject)) {
|
if c.perms != nil && (c.perms.pub.allow != nil || c.perms.pub.deny != nil) && !c.pubAllowedFullCheck(string(c.pa.subject), true, true) {
|
||||||
|
c.mu.Unlock()
|
||||||
c.pubPermissionViolation(c.pa.subject)
|
c.pubPermissionViolation(c.pa.subject)
|
||||||
return false, true
|
return false, true
|
||||||
}
|
}
|
||||||
|
c.mu.Unlock()
|
||||||
|
|
||||||
// Now check for reserved replies. These are used for service imports.
|
// Now check for reserved replies. These are used for service imports.
|
||||||
if c.kind == CLIENT && len(c.pa.reply) > 0 && isReservedReply(c.pa.reply) {
|
if c.kind == CLIENT && len(c.pa.reply) > 0 && isReservedReply(c.pa.reply) {
|
||||||
@@ -3611,10 +3612,10 @@ func (c *client) processInboundClientMsg(msg []byte) (bool, bool) {
|
|||||||
// performance impact reported in our bench)
|
// performance impact reported in our bench)
|
||||||
var isGWRouted bool
|
var isGWRouted bool
|
||||||
if c.kind != CLIENT {
|
if c.kind != CLIENT {
|
||||||
if atomic.LoadInt32(&c.acc.gwReplyMapping.check) > 0 {
|
if atomic.LoadInt32(&acc.gwReplyMapping.check) > 0 {
|
||||||
c.acc.mu.RLock()
|
acc.mu.RLock()
|
||||||
c.pa.subject, isGWRouted = c.acc.gwReplyMapping.get(c.pa.subject)
|
c.pa.subject, isGWRouted = acc.gwReplyMapping.get(c.pa.subject)
|
||||||
c.acc.mu.RUnlock()
|
acc.mu.RUnlock()
|
||||||
}
|
}
|
||||||
} else if atomic.LoadInt32(&c.gwReplyMapping.check) > 0 {
|
} else if atomic.LoadInt32(&c.gwReplyMapping.check) > 0 {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
@@ -3657,7 +3658,7 @@ func (c *client) processInboundClientMsg(msg []byte) (bool, bool) {
|
|||||||
var r *SublistResult
|
var r *SublistResult
|
||||||
var ok bool
|
var ok bool
|
||||||
|
|
||||||
genid := atomic.LoadUint64(&c.acc.sl.genid)
|
genid := atomic.LoadUint64(genidAddr)
|
||||||
if genid == c.in.genid && c.in.results != nil {
|
if genid == c.in.genid && c.in.results != nil {
|
||||||
r, ok = c.in.results[string(c.pa.subject)]
|
r, ok = c.in.results[string(c.pa.subject)]
|
||||||
} else {
|
} else {
|
||||||
@@ -3668,15 +3669,17 @@ func (c *client) processInboundClientMsg(msg []byte) (bool, bool) {
|
|||||||
|
|
||||||
// Go back to the sublist data structure.
|
// Go back to the sublist data structure.
|
||||||
if !ok {
|
if !ok {
|
||||||
r = c.acc.sl.Match(string(c.pa.subject))
|
r = acc.sl.Match(string(c.pa.subject))
|
||||||
c.in.results[string(c.pa.subject)] = r
|
if len(r.psubs)+len(r.qsubs) > 0 {
|
||||||
// Prune the results cache. Keeps us from unbounded growth. Random delete.
|
c.in.results[string(c.pa.subject)] = r
|
||||||
if len(c.in.results) > maxResultCacheSize {
|
// Prune the results cache. Keeps us from unbounded growth. Random delete.
|
||||||
n := 0
|
if len(c.in.results) > maxResultCacheSize {
|
||||||
for subject := range c.in.results {
|
n := 0
|
||||||
delete(c.in.results, subject)
|
for subject := range c.in.results {
|
||||||
if n++; n > pruneSize {
|
delete(c.in.results, subject)
|
||||||
break
|
if n++; n > pruneSize {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3699,7 +3702,7 @@ func (c *client) processInboundClientMsg(msg []byte) (bool, bool) {
|
|||||||
atomic.LoadInt64(&c.srv.gateway.totalQSubs) > 0 {
|
atomic.LoadInt64(&c.srv.gateway.totalQSubs) > 0 {
|
||||||
flag |= pmrCollectQueueNames
|
flag |= pmrCollectQueueNames
|
||||||
}
|
}
|
||||||
didDeliver, qnames = c.processMsgResults(c.acc, r, msg, c.pa.deliver, c.pa.subject, c.pa.reply, flag)
|
didDeliver, qnames = c.processMsgResults(acc, r, msg, c.pa.deliver, c.pa.subject, c.pa.reply, flag)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now deal with gateways
|
// Now deal with gateways
|
||||||
@@ -3709,7 +3712,7 @@ func (c *client) processInboundClientMsg(msg []byte) (bool, bool) {
|
|||||||
reply = append(reply, '@')
|
reply = append(reply, '@')
|
||||||
reply = append(reply, c.pa.deliver...)
|
reply = append(reply, c.pa.deliver...)
|
||||||
}
|
}
|
||||||
didDeliver = c.sendMsgToGateways(c.acc, msg, c.pa.subject, reply, qnames) || didDeliver
|
didDeliver = c.sendMsgToGateways(acc, msg, c.pa.subject, reply, qnames) || didDeliver
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check to see if we did not deliver to anyone and the client has a reply subject set
|
// Check to see if we did not deliver to anyone and the client has a reply subject set
|
||||||
@@ -3915,6 +3918,7 @@ func (c *client) processServiceImport(si *serviceImport, acc *Account, msg []byt
|
|||||||
checkJS = true
|
checkJS = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
siAcc := si.acc
|
||||||
acc.mu.RUnlock()
|
acc.mu.RUnlock()
|
||||||
|
|
||||||
// We have a special case where JetStream pulls in all service imports through one export.
|
// We have a special case where JetStream pulls in all service imports through one export.
|
||||||
@@ -3945,7 +3949,7 @@ func (c *client) processServiceImport(si *serviceImport, acc *Account, msg []byt
|
|||||||
}
|
}
|
||||||
} else if !isResponse && si.latency != nil && tracking {
|
} else if !isResponse && si.latency != nil && tracking {
|
||||||
// Check to see if this was a bad request with no reply and we were supposed to be tracking.
|
// Check to see if this was a bad request with no reply and we were supposed to be tracking.
|
||||||
si.acc.sendBadRequestTrackingLatency(si, c, headers)
|
siAcc.sendBadRequestTrackingLatency(si, c, headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send tracking info here if we are tracking this response.
|
// Send tracking info here if we are tracking this response.
|
||||||
@@ -3973,7 +3977,7 @@ func (c *client) processServiceImport(si *serviceImport, acc *Account, msg []byt
|
|||||||
// Now check to see if this account has mappings that could affect the service import.
|
// Now check to see if this account has mappings that could affect the service import.
|
||||||
// Can't use non-locked trick like in processInboundClientMsg, so just call into selectMappedSubject
|
// Can't use non-locked trick like in processInboundClientMsg, so just call into selectMappedSubject
|
||||||
// so we only lock once.
|
// so we only lock once.
|
||||||
nsubj, changed := si.acc.selectMappedSubject(to)
|
nsubj, changed := siAcc.selectMappedSubject(to)
|
||||||
if changed {
|
if changed {
|
||||||
c.pa.mapped = []byte(to)
|
c.pa.mapped = []byte(to)
|
||||||
to = nsubj
|
to = nsubj
|
||||||
@@ -3990,7 +3994,7 @@ func (c *client) processServiceImport(si *serviceImport, acc *Account, msg []byt
|
|||||||
// Place our client info for the request in the original message.
|
// Place our client info for the request in the original message.
|
||||||
// This will survive going across routes, etc.
|
// This will survive going across routes, etc.
|
||||||
if !isResponse {
|
if !isResponse {
|
||||||
isSysImport := si.acc == c.srv.SystemAccount()
|
isSysImport := siAcc == c.srv.SystemAccount()
|
||||||
var ci *ClientInfo
|
var ci *ClientInfo
|
||||||
if hadPrevSi && c.pa.hdr >= 0 {
|
if hadPrevSi && c.pa.hdr >= 0 {
|
||||||
var cis ClientInfo
|
var cis ClientInfo
|
||||||
@@ -4031,11 +4035,11 @@ func (c *client) processServiceImport(si *serviceImport, acc *Account, msg []byt
|
|||||||
c.pa.reply = nrr
|
c.pa.reply = nrr
|
||||||
|
|
||||||
if changed && c.isMqtt() && c.pa.hdr > 0 {
|
if changed && c.isMqtt() && c.pa.hdr > 0 {
|
||||||
c.srv.mqttStoreQoS1MsgForAccountOnNewSubject(c.pa.hdr, msg, si.acc.GetName(), to)
|
c.srv.mqttStoreQoS1MsgForAccountOnNewSubject(c.pa.hdr, msg, siAcc.GetName(), to)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME(dlc) - Do L1 cache trick like normal client?
|
// FIXME(dlc) - Do L1 cache trick like normal client?
|
||||||
rr := si.acc.sl.Match(to)
|
rr := siAcc.sl.Match(to)
|
||||||
|
|
||||||
// If we are a route or gateway or leafnode and this message is flipped to a queue subscriber we
|
// If we are a route or gateway or leafnode and this message is flipped to a queue subscriber we
|
||||||
// need to handle that since the processMsgResults will want a queue filter.
|
// need to handle that since the processMsgResults will want a queue filter.
|
||||||
@@ -4060,10 +4064,10 @@ func (c *client) processServiceImport(si *serviceImport, acc *Account, msg []byt
|
|||||||
if c.srv.gateway.enabled {
|
if c.srv.gateway.enabled {
|
||||||
flags |= pmrCollectQueueNames
|
flags |= pmrCollectQueueNames
|
||||||
var queues [][]byte
|
var queues [][]byte
|
||||||
didDeliver, queues = c.processMsgResults(si.acc, rr, msg, c.pa.deliver, []byte(to), nrr, flags)
|
didDeliver, queues = c.processMsgResults(siAcc, rr, msg, c.pa.deliver, []byte(to), nrr, flags)
|
||||||
didDeliver = c.sendMsgToGateways(si.acc, msg, []byte(to), nrr, queues) || didDeliver
|
didDeliver = c.sendMsgToGateways(siAcc, msg, []byte(to), nrr, queues) || didDeliver
|
||||||
} else {
|
} else {
|
||||||
didDeliver, _ = c.processMsgResults(si.acc, rr, msg, c.pa.deliver, []byte(to), nrr, flags)
|
didDeliver, _ = c.processMsgResults(siAcc, rr, msg, c.pa.deliver, []byte(to), nrr, flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore to original values.
|
// Restore to original values.
|
||||||
@@ -4096,7 +4100,7 @@ func (c *client) processServiceImport(si *serviceImport, acc *Account, msg []byt
|
|||||||
} else {
|
} else {
|
||||||
// This is a main import and since we could not even deliver to the exporting account
|
// This is a main import and since we could not even deliver to the exporting account
|
||||||
// go ahead and remove the respServiceImport we created above.
|
// go ahead and remove the respServiceImport we created above.
|
||||||
si.acc.removeRespServiceImport(rsi, reason)
|
siAcc.removeRespServiceImport(rsi, reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -41,7 +41,7 @@ var (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// VERSION is the current version for the server.
|
// VERSION is the current version for the server.
|
||||||
VERSION = "2.9.19"
|
VERSION = "2.9.21"
|
||||||
|
|
||||||
// PROTO is the currently supported protocol.
|
// PROTO is the currently supported protocol.
|
||||||
// 0 was the original
|
// 0 was the original
|
||||||
|
|||||||
+1
-1
@@ -3506,7 +3506,7 @@ func (o *consumer) loopAndGatherMsgs(qch chan struct{}) {
|
|||||||
if err == ErrStoreEOF {
|
if err == ErrStoreEOF {
|
||||||
o.checkNumPendingOnEOF()
|
o.checkNumPendingOnEOF()
|
||||||
}
|
}
|
||||||
if err == ErrStoreMsgNotFound || err == ErrStoreEOF || err == errMaxAckPending || err == errPartialCache {
|
if err == ErrStoreMsgNotFound || err == errDeletedMsg || err == ErrStoreEOF || err == errMaxAckPending || err == errPartialCache {
|
||||||
goto waitForMsgs
|
goto waitForMsgs
|
||||||
} else {
|
} else {
|
||||||
s.Errorf("Received an error looking up message for consumer: %v", err)
|
s.Errorf("Received an error looking up message for consumer: %v", err)
|
||||||
|
|||||||
+111
-26
@@ -17,6 +17,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -30,7 +31,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/klauspost/compress/s2"
|
"github.com/klauspost/compress/s2"
|
||||||
|
|
||||||
"github.com/nats-io/jwt/v2"
|
"github.com/nats-io/jwt/v2"
|
||||||
|
"github.com/nats-io/nats-server/v2/server/certidp"
|
||||||
"github.com/nats-io/nats-server/v2/server/pse"
|
"github.com/nats-io/nats-server/v2/server/pse"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -78,6 +81,9 @@ const (
|
|||||||
|
|
||||||
accReqTokens = 5
|
accReqTokens = 5
|
||||||
accReqAccIndex = 3
|
accReqAccIndex = 3
|
||||||
|
|
||||||
|
ocspPeerRejectEventSubj = "$SYS.SERVER.%s.OCSP.PEER.CONN.REJECT"
|
||||||
|
ocspPeerChainlinkInvalidEventSubj = "$SYS.SERVER.%s.OCSP.PEER.LINK.INVALID"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FIXME(dlc) - make configurable.
|
// FIXME(dlc) - make configurable.
|
||||||
@@ -151,6 +157,34 @@ type DisconnectEventMsg struct {
|
|||||||
// DisconnectEventMsgType is the schema type for DisconnectEventMsg
|
// DisconnectEventMsgType is the schema type for DisconnectEventMsg
|
||||||
const DisconnectEventMsgType = "io.nats.server.advisory.v1.client_disconnect"
|
const DisconnectEventMsgType = "io.nats.server.advisory.v1.client_disconnect"
|
||||||
|
|
||||||
|
// OCSPPeerRejectEventMsg is sent when a peer TLS handshake is ultimately rejected due to OCSP invalidation.
|
||||||
|
// A "peer" can be an inbound client connection or a leaf connection to a remote server. Peer in event payload
|
||||||
|
// is always the peer's (TLS) leaf cert, which may or may be the invalid cert (See also OCSPPeerChainlinkInvalidEventMsg)
|
||||||
|
type OCSPPeerRejectEventMsg struct {
|
||||||
|
TypedEvent
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
Peer certidp.CertInfo `json:"peer"`
|
||||||
|
Server ServerInfo `json:"server"`
|
||||||
|
Reason string `json:"reason"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// OCSPPeerRejectEventMsgType is the schema type for OCSPPeerRejectEventMsg
|
||||||
|
const OCSPPeerRejectEventMsgType = "io.nats.server.advisory.v1.ocsp_peer_reject"
|
||||||
|
|
||||||
|
// OCSPPeerChainlinkInvalidEventMsg is sent when a certificate (link) in a valid TLS chain is found to be OCSP invalid
|
||||||
|
// during a peer TLS handshake. A "peer" can be an inbound client connection or a leaf connection to a remote server.
|
||||||
|
// Peer and Link may be the same if the invalid cert was the peer's leaf cert
|
||||||
|
type OCSPPeerChainlinkInvalidEventMsg struct {
|
||||||
|
TypedEvent
|
||||||
|
Link certidp.CertInfo `json:"link"`
|
||||||
|
Peer certidp.CertInfo `json:"peer"`
|
||||||
|
Server ServerInfo `json:"server"`
|
||||||
|
Reason string `json:"reason"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// OCSPPeerChainlinkInvalidEventMsgType is the schema type for OCSPPeerChainlinkInvalidEventMsg
|
||||||
|
const OCSPPeerChainlinkInvalidEventMsgType = "io.nats.server.advisory.v1.ocsp_peer_link_invalid"
|
||||||
|
|
||||||
// AccountNumConns is an event that will be sent from a server that is tracking
|
// AccountNumConns is an event that will be sent from a server that is tracking
|
||||||
// a given account when the number of connections changes. It will also HB
|
// a given account when the number of connections changes. It will also HB
|
||||||
// updates in the absence of any changes.
|
// updates in the absence of any changes.
|
||||||
@@ -843,35 +877,15 @@ func getHash(name string) string {
|
|||||||
return getHashSize(name, sysHashLen)
|
return getHashSize(name, sysHashLen)
|
||||||
}
|
}
|
||||||
|
|
||||||
var nameToHashSize8 = sync.Map{}
|
|
||||||
var nameToHashSize6 = sync.Map{}
|
|
||||||
|
|
||||||
// Computes a hash for the given `name`. The result will be `size` characters long.
|
// Computes a hash for the given `name`. The result will be `size` characters long.
|
||||||
func getHashSize(name string, size int) string {
|
func getHashSize(name string, size int) string {
|
||||||
compute := func() string {
|
sha := sha256.New()
|
||||||
sha := sha256.New()
|
sha.Write([]byte(name))
|
||||||
sha.Write([]byte(name))
|
b := sha.Sum(nil)
|
||||||
b := sha.Sum(nil)
|
for i := 0; i < size; i++ {
|
||||||
for i := 0; i < size; i++ {
|
b[i] = digits[int(b[i]%base)]
|
||||||
b[i] = digits[int(b[i]%base)]
|
|
||||||
}
|
|
||||||
return string(b[:size])
|
|
||||||
}
|
}
|
||||||
var m *sync.Map
|
return string(b[:size])
|
||||||
switch size {
|
|
||||||
case 8:
|
|
||||||
m = &nameToHashSize8
|
|
||||||
case 6:
|
|
||||||
m = &nameToHashSize6
|
|
||||||
default:
|
|
||||||
return compute()
|
|
||||||
}
|
|
||||||
if v, ok := m.Load(name); ok {
|
|
||||||
return v.(string)
|
|
||||||
}
|
|
||||||
h := compute()
|
|
||||||
m.Store(name, h)
|
|
||||||
return h
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the node name for this server which is a hash of the server name.
|
// Returns the node name for this server which is a hash of the server name.
|
||||||
@@ -2488,3 +2502,74 @@ func (s *Server) wrapChk(f func()) func() {
|
|||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sendOCSPPeerRejectEvent sends a system level event to system account when a peer connection is
|
||||||
|
// rejected due to OCSP invalid status of its trust chain(s).
|
||||||
|
func (s *Server) sendOCSPPeerRejectEvent(kind string, peer *x509.Certificate, reason string) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
if !s.eventsEnabled() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if peer == nil {
|
||||||
|
s.Errorf(certidp.ErrPeerEmptyNoEvent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
eid := s.nextEventID()
|
||||||
|
now := time.Now().UTC()
|
||||||
|
m := OCSPPeerRejectEventMsg{
|
||||||
|
TypedEvent: TypedEvent{
|
||||||
|
Type: OCSPPeerRejectEventMsgType,
|
||||||
|
ID: eid,
|
||||||
|
Time: now,
|
||||||
|
},
|
||||||
|
Kind: kind,
|
||||||
|
Peer: certidp.CertInfo{
|
||||||
|
Subject: certidp.GetSubjectDNForm(peer),
|
||||||
|
Issuer: certidp.GetIssuerDNForm(peer),
|
||||||
|
Fingerprint: certidp.GenerateFingerprint(peer),
|
||||||
|
Raw: peer.Raw,
|
||||||
|
},
|
||||||
|
Reason: reason,
|
||||||
|
}
|
||||||
|
subj := fmt.Sprintf(ocspPeerRejectEventSubj, s.info.ID)
|
||||||
|
s.sendInternalMsg(subj, _EMPTY_, &m.Server, &m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendOCSPPeerChainlinkInvalidEvent sends a system level event to system account when a link in a peer's trust chain
|
||||||
|
// is OCSP invalid.
|
||||||
|
func (s *Server) sendOCSPPeerChainlinkInvalidEvent(peer *x509.Certificate, link *x509.Certificate, reason string) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
if !s.eventsEnabled() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if peer == nil || link == nil {
|
||||||
|
s.Errorf(certidp.ErrPeerEmptyNoEvent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
eid := s.nextEventID()
|
||||||
|
now := time.Now().UTC()
|
||||||
|
m := OCSPPeerChainlinkInvalidEventMsg{
|
||||||
|
TypedEvent: TypedEvent{
|
||||||
|
Type: OCSPPeerChainlinkInvalidEventMsgType,
|
||||||
|
ID: eid,
|
||||||
|
Time: now,
|
||||||
|
},
|
||||||
|
Link: certidp.CertInfo{
|
||||||
|
Subject: certidp.GetSubjectDNForm(link),
|
||||||
|
Issuer: certidp.GetIssuerDNForm(link),
|
||||||
|
Fingerprint: certidp.GenerateFingerprint(link),
|
||||||
|
Raw: link.Raw,
|
||||||
|
},
|
||||||
|
Peer: certidp.CertInfo{
|
||||||
|
Subject: certidp.GetSubjectDNForm(peer),
|
||||||
|
Issuer: certidp.GetIssuerDNForm(peer),
|
||||||
|
Fingerprint: certidp.GenerateFingerprint(peer),
|
||||||
|
Raw: peer.Raw,
|
||||||
|
},
|
||||||
|
Reason: reason,
|
||||||
|
}
|
||||||
|
subj := fmt.Sprintf(ocspPeerChainlinkInvalidEventSubj, s.info.ID)
|
||||||
|
s.sendInternalMsg(subj, _EMPTY_, &m.Server, &m)
|
||||||
|
}
|
||||||
|
|||||||
+134
-43
@@ -275,6 +275,8 @@ const (
|
|||||||
wiThresh = int64(30 * time.Second)
|
wiThresh = int64(30 * time.Second)
|
||||||
// Time threshold to write index info for non FIFO cases
|
// Time threshold to write index info for non FIFO cases
|
||||||
winfThresh = int64(2 * time.Second)
|
winfThresh = int64(2 * time.Second)
|
||||||
|
// Checksum size for hash for msg records.
|
||||||
|
recordHashSize = 8
|
||||||
)
|
)
|
||||||
|
|
||||||
func newFileStore(fcfg FileStoreConfig, cfg StreamConfig) (*fileStore, error) {
|
func newFileStore(fcfg FileStoreConfig, cfg StreamConfig) (*fileStore, error) {
|
||||||
@@ -349,6 +351,14 @@ func newFileStoreWithCreated(fcfg FileStoreConfig, cfg StreamConfig, created tim
|
|||||||
return nil, fmt.Errorf("could not create hash: %v", err)
|
return nil, fmt.Errorf("could not create hash: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
keyFile := filepath.Join(fs.fcfg.StoreDir, JetStreamMetaFileKey)
|
||||||
|
// Make sure we do not have an encrypted store underneath of us but no main key.
|
||||||
|
if fs.prf == nil {
|
||||||
|
if _, err := os.Stat(keyFile); err == nil {
|
||||||
|
return nil, errNoMainKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Recover our message state.
|
// Recover our message state.
|
||||||
if err := fs.recoverMsgs(); err != nil {
|
if err := fs.recoverMsgs(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -366,7 +376,6 @@ func newFileStoreWithCreated(fcfg FileStoreConfig, cfg StreamConfig, created tim
|
|||||||
// If we expect to be encrypted check that what we are restoring is not plaintext.
|
// If we expect to be encrypted check that what we are restoring is not plaintext.
|
||||||
// This can happen on snapshot restores or conversions.
|
// This can happen on snapshot restores or conversions.
|
||||||
if fs.prf != nil {
|
if fs.prf != nil {
|
||||||
keyFile := filepath.Join(fs.fcfg.StoreDir, JetStreamMetaFileKey)
|
|
||||||
if _, err := os.Stat(keyFile); err != nil && os.IsNotExist(err) {
|
if _, err := os.Stat(keyFile); err != nil && os.IsNotExist(err) {
|
||||||
if err := fs.writeStreamMeta(); err != nil {
|
if err := fs.writeStreamMeta(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -964,6 +973,10 @@ func (mb *msgBlock) rebuildState() (*LostStreamData, error) {
|
|||||||
func (mb *msgBlock) rebuildStateLocked() (*LostStreamData, error) {
|
func (mb *msgBlock) rebuildStateLocked() (*LostStreamData, error) {
|
||||||
startLastSeq := mb.last.seq
|
startLastSeq := mb.last.seq
|
||||||
|
|
||||||
|
// Remove the .fss file and clear any cache we have set.
|
||||||
|
mb.clearCacheAndOffset()
|
||||||
|
mb.removePerSubjectInfoLocked()
|
||||||
|
|
||||||
buf, err := mb.loadBlock(nil)
|
buf, err := mb.loadBlock(nil)
|
||||||
if err != nil || len(buf) == 0 {
|
if err != nil || len(buf) == 0 {
|
||||||
var ld *LostStreamData
|
var ld *LostStreamData
|
||||||
@@ -989,9 +1002,6 @@ func (mb *msgBlock) rebuildStateLocked() (*LostStreamData, error) {
|
|||||||
mb.last.seq, mb.last.ts = 0, 0
|
mb.last.seq, mb.last.ts = 0, 0
|
||||||
firstNeedsSet := true
|
firstNeedsSet := true
|
||||||
|
|
||||||
// Remove the .fss file from disk.
|
|
||||||
mb.removePerSubjectInfoLocked()
|
|
||||||
|
|
||||||
// Check if we need to decrypt.
|
// Check if we need to decrypt.
|
||||||
if mb.bek != nil && len(buf) > 0 {
|
if mb.bek != nil && len(buf) > 0 {
|
||||||
// Recreate to reset counter.
|
// Recreate to reset counter.
|
||||||
@@ -1063,12 +1073,7 @@ func (mb *msgBlock) rebuildStateLocked() (*LostStreamData, error) {
|
|||||||
rl &^= hbit
|
rl &^= hbit
|
||||||
dlen := int(rl) - msgHdrSize
|
dlen := int(rl) - msgHdrSize
|
||||||
// Do some quick sanity checks here.
|
// Do some quick sanity checks here.
|
||||||
if dlen < 0 || int(slen) > (dlen-8) || dlen > int(rl) || rl > rlBadThresh {
|
if dlen < 0 || int(slen) > (dlen-recordHashSize) || dlen > int(rl) || index+rl > lbuf || rl > rlBadThresh {
|
||||||
truncate(index)
|
|
||||||
return gatherLost(lbuf - index), errBadMsg
|
|
||||||
}
|
|
||||||
|
|
||||||
if index+rl > lbuf {
|
|
||||||
truncate(index)
|
truncate(index)
|
||||||
return gatherLost(lbuf - index), errBadMsg
|
return gatherLost(lbuf - index), errBadMsg
|
||||||
}
|
}
|
||||||
@@ -1084,15 +1089,17 @@ func (mb *msgBlock) rebuildStateLocked() (*LostStreamData, error) {
|
|||||||
addToDmap(seq)
|
addToDmap(seq)
|
||||||
}
|
}
|
||||||
index += rl
|
index += rl
|
||||||
mb.last.seq = seq
|
if seq >= mb.first.seq {
|
||||||
mb.last.ts = ts
|
mb.last.seq = seq
|
||||||
|
mb.last.ts = ts
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is for when we have index info that adjusts for deleted messages
|
// This is for when we have index info that adjusts for deleted messages
|
||||||
// at the head. So the first.seq will be already set here. If this is larger
|
// at the head. So the first.seq will be already set here. If this is larger
|
||||||
// replace what we have with this seq.
|
// replace what we have with this seq.
|
||||||
if firstNeedsSet && seq > mb.first.seq {
|
if firstNeedsSet && seq >= mb.first.seq {
|
||||||
firstNeedsSet, mb.first.seq, mb.first.ts = false, seq, ts
|
firstNeedsSet, mb.first.seq, mb.first.ts = false, seq, ts
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1112,12 +1119,12 @@ func (mb *msgBlock) rebuildStateLocked() (*LostStreamData, error) {
|
|||||||
hh.Write(hdr[4:20])
|
hh.Write(hdr[4:20])
|
||||||
hh.Write(data[:slen])
|
hh.Write(data[:slen])
|
||||||
if hasHeaders {
|
if hasHeaders {
|
||||||
hh.Write(data[slen+4 : dlen-8])
|
hh.Write(data[slen+4 : dlen-recordHashSize])
|
||||||
} else {
|
} else {
|
||||||
hh.Write(data[slen : dlen-8])
|
hh.Write(data[slen : dlen-recordHashSize])
|
||||||
}
|
}
|
||||||
checksum := hh.Sum(nil)
|
checksum := hh.Sum(nil)
|
||||||
if !bytes.Equal(checksum, data[len(data)-8:]) {
|
if !bytes.Equal(checksum, data[len(data)-recordHashSize:]) {
|
||||||
truncate(index)
|
truncate(index)
|
||||||
return gatherLost(lbuf - index), errBadMsg
|
return gatherLost(lbuf - index), errBadMsg
|
||||||
}
|
}
|
||||||
@@ -1158,6 +1165,11 @@ func (mb *msgBlock) rebuildStateLocked() (*LostStreamData, error) {
|
|||||||
mb.last.seq = mb.first.seq - 1
|
mb.last.seq = mb.first.seq - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update our fss file if needed.
|
||||||
|
if len(mb.fss) > 0 {
|
||||||
|
mb.writePerSubjectInfo()
|
||||||
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1868,12 +1880,24 @@ func (fs *fileStore) NumPending(sseq uint64, filter string, lastPerSubject bool)
|
|||||||
seqStart, _ = fs.selectMsgBlockWithIndex(sseq)
|
seqStart, _ = fs.selectMsgBlockWithIndex(sseq)
|
||||||
}
|
}
|
||||||
|
|
||||||
tsa := [32]string{}
|
var tsa, fsa [32]string
|
||||||
fsa := [32]string{}
|
|
||||||
fts := tokenizeSubjectIntoSlice(fsa[:0], filter)
|
fts := tokenizeSubjectIntoSlice(fsa[:0], filter)
|
||||||
isAll := filter == _EMPTY_ || filter == fwcs
|
isAll := filter == _EMPTY_ || filter == fwcs
|
||||||
wc := subjectHasWildcard(filter)
|
wc := subjectHasWildcard(filter)
|
||||||
|
|
||||||
|
// See if filter was provided but its the only subject.
|
||||||
|
if !isAll && !wc && len(fs.psim) == 1 && fs.psim[filter] != nil {
|
||||||
|
isAll = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we are isAll and have no deleted we can do a simpler calculation.
|
||||||
|
if isAll && (fs.state.LastSeq-fs.state.FirstSeq+1) == fs.state.Msgs {
|
||||||
|
if sseq == 0 {
|
||||||
|
return fs.state.Msgs, validThrough
|
||||||
|
}
|
||||||
|
return fs.state.LastSeq - sseq + 1, validThrough
|
||||||
|
}
|
||||||
|
|
||||||
isMatch := func(subj string) bool {
|
isMatch := func(subj string) bool {
|
||||||
if isAll {
|
if isAll {
|
||||||
return true
|
return true
|
||||||
@@ -1900,6 +1924,7 @@ func (fs *fileStore) NumPending(sseq uint64, filter string, lastPerSubject bool)
|
|||||||
var t uint64
|
var t uint64
|
||||||
if isAll && sseq <= mb.first.seq {
|
if isAll && sseq <= mb.first.seq {
|
||||||
if lastPerSubject {
|
if lastPerSubject {
|
||||||
|
mb.ensurePerSubjectInfoLoaded()
|
||||||
for subj := range mb.fss {
|
for subj := range mb.fss {
|
||||||
if !seen[subj] {
|
if !seen[subj] {
|
||||||
total++
|
total++
|
||||||
@@ -2023,16 +2048,20 @@ func (fs *fileStore) NumPending(sseq uint64, filter string, lastPerSubject bool)
|
|||||||
mb.mu.Lock()
|
mb.mu.Lock()
|
||||||
// Check if we should include all of this block in adjusting. If so work with metadata.
|
// Check if we should include all of this block in adjusting. If so work with metadata.
|
||||||
if sseq > mb.last.seq {
|
if sseq > mb.last.seq {
|
||||||
// We need to adjust for all matches in this block.
|
if isAll && !lastPerSubject {
|
||||||
// We will scan fss state vs messages themselves.
|
adjust += mb.msgs
|
||||||
// Make sure we have fss loaded.
|
} else {
|
||||||
mb.ensurePerSubjectInfoLoaded()
|
// We need to adjust for all matches in this block.
|
||||||
for subj, ss := range mb.fss {
|
// We will scan fss state vs messages themselves.
|
||||||
if isMatch(subj) {
|
// Make sure we have fss loaded.
|
||||||
if lastPerSubject {
|
mb.ensurePerSubjectInfoLoaded()
|
||||||
adjust++
|
for subj, ss := range mb.fss {
|
||||||
} else {
|
if isMatch(subj) {
|
||||||
adjust += ss.Msgs
|
if lastPerSubject {
|
||||||
|
adjust++
|
||||||
|
} else {
|
||||||
|
adjust += ss.Msgs
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2574,14 +2603,42 @@ func (fs *fileStore) enforceMsgPerSubjectLimit() {
|
|||||||
fs.scb = nil
|
fs.scb = nil
|
||||||
defer func() { fs.scb = cb }()
|
defer func() { fs.scb = cb }()
|
||||||
|
|
||||||
|
var numMsgs uint64
|
||||||
|
|
||||||
// collect all that are not correct.
|
// collect all that are not correct.
|
||||||
needAttention := make(map[string]*psi)
|
needAttention := make(map[string]*psi)
|
||||||
for subj, psi := range fs.psim {
|
for subj, psi := range fs.psim {
|
||||||
|
numMsgs += psi.total
|
||||||
if psi.total > maxMsgsPer {
|
if psi.total > maxMsgsPer {
|
||||||
needAttention[subj] = psi
|
needAttention[subj] = psi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We had an issue with a use case where psim (and hence fss) were correct but idx was not and was not properly being caught.
|
||||||
|
// So do a quick sanity check here. If we detect a skew do a rebuild then re-check.
|
||||||
|
if numMsgs != fs.state.Msgs {
|
||||||
|
// Clear any global subject state.
|
||||||
|
fs.psim = make(map[string]*psi)
|
||||||
|
for _, mb := range fs.blks {
|
||||||
|
mb.removeIndexFile()
|
||||||
|
ld, err := mb.rebuildState()
|
||||||
|
mb.writeIndexInfo()
|
||||||
|
if err != nil && ld != nil {
|
||||||
|
fs.addLostData(ld)
|
||||||
|
}
|
||||||
|
fs.populateGlobalPerSubjectInfo(mb)
|
||||||
|
}
|
||||||
|
// Rebuild fs state too.
|
||||||
|
fs.rebuildStateLocked(nil)
|
||||||
|
// Need to redo blocks that need attention.
|
||||||
|
needAttention = make(map[string]*psi)
|
||||||
|
for subj, psi := range fs.psim {
|
||||||
|
if psi.total > maxMsgsPer {
|
||||||
|
needAttention[subj] = psi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Collect all the msgBlks we alter.
|
// Collect all the msgBlks we alter.
|
||||||
blks := make(map[*msgBlock]struct{})
|
blks := make(map[*msgBlock]struct{})
|
||||||
|
|
||||||
@@ -3026,8 +3083,7 @@ func (mb *msgBlock) compact() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close cache and index file and wipe delete map, then rebuild.
|
// Remove index file and wipe delete map, then rebuild.
|
||||||
mb.clearCacheAndOffset()
|
|
||||||
mb.removeIndexFileLocked()
|
mb.removeIndexFileLocked()
|
||||||
mb.deleteDmap()
|
mb.deleteDmap()
|
||||||
mb.rebuildStateLocked()
|
mb.rebuildStateLocked()
|
||||||
@@ -3053,6 +3109,11 @@ func (mb *msgBlock) slotInfo(slot int) (uint32, uint32, bool, error) {
|
|||||||
bi := mb.cache.idx[slot]
|
bi := mb.cache.idx[slot]
|
||||||
ri, hashChecked := (bi &^ hbit), (bi&hbit) != 0
|
ri, hashChecked := (bi &^ hbit), (bi&hbit) != 0
|
||||||
|
|
||||||
|
// If this is a deleted slot return here.
|
||||||
|
if bi == dbit {
|
||||||
|
return 0, 0, false, errDeletedMsg
|
||||||
|
}
|
||||||
|
|
||||||
// Determine record length
|
// Determine record length
|
||||||
var rl uint32
|
var rl uint32
|
||||||
if len(mb.cache.idx) > slot+1 {
|
if len(mb.cache.idx) > slot+1 {
|
||||||
@@ -3998,7 +4059,7 @@ func (fs *fileStore) selectMsgBlockForStart(minTime time.Time) *msgBlock {
|
|||||||
func (mb *msgBlock) indexCacheBuf(buf []byte) error {
|
func (mb *msgBlock) indexCacheBuf(buf []byte) error {
|
||||||
var le = binary.LittleEndian
|
var le = binary.LittleEndian
|
||||||
|
|
||||||
var fseq uint64
|
var fseq, pseq uint64
|
||||||
var idx []uint32
|
var idx []uint32
|
||||||
var index uint32
|
var index uint32
|
||||||
|
|
||||||
@@ -4031,7 +4092,7 @@ func (mb *msgBlock) indexCacheBuf(buf []byte) error {
|
|||||||
dlen := int(rl) - msgHdrSize
|
dlen := int(rl) - msgHdrSize
|
||||||
|
|
||||||
// Do some quick sanity checks here.
|
// Do some quick sanity checks here.
|
||||||
if dlen < 0 || int(slen) > dlen || dlen > int(rl) || index+rl > lbuf || rl > 32*1024*1024 {
|
if dlen < 0 || int(slen) > (dlen-recordHashSize) || dlen > int(rl) || index+rl > lbuf || rl > rlBadThresh {
|
||||||
// This means something is off.
|
// This means something is off.
|
||||||
// TODO(dlc) - Add into bad list?
|
// TODO(dlc) - Add into bad list?
|
||||||
return errCorruptState
|
return errCorruptState
|
||||||
@@ -4039,15 +4100,31 @@ func (mb *msgBlock) indexCacheBuf(buf []byte) error {
|
|||||||
|
|
||||||
// Clear erase bit.
|
// Clear erase bit.
|
||||||
seq = seq &^ ebit
|
seq = seq &^ ebit
|
||||||
// Adjust if we guessed wrong.
|
|
||||||
if seq != 0 && seq < fseq {
|
|
||||||
fseq = seq
|
|
||||||
}
|
|
||||||
// We defer checksum checks to individual msg cache lookups to amortorize costs and
|
// We defer checksum checks to individual msg cache lookups to amortorize costs and
|
||||||
// not introduce latency for first message from a newly loaded block.
|
// not introduce latency for first message from a newly loaded block.
|
||||||
idx = append(idx, index)
|
if seq >= mb.first.seq {
|
||||||
mb.cache.lrl = uint32(rl)
|
// Track that we do not have holes.
|
||||||
index += mb.cache.lrl
|
// Not expected but did see it in the field.
|
||||||
|
if pseq > 0 && seq != pseq+1 {
|
||||||
|
if mb.dmap == nil {
|
||||||
|
mb.dmap = make(map[uint64]struct{})
|
||||||
|
}
|
||||||
|
for dseq := pseq + 1; dseq < seq; dseq++ {
|
||||||
|
idx = append(idx, dbit)
|
||||||
|
mb.dmap[dseq] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pseq = seq
|
||||||
|
|
||||||
|
idx = append(idx, index)
|
||||||
|
mb.cache.lrl = uint32(rl)
|
||||||
|
// Adjust if we guessed wrong.
|
||||||
|
if seq != 0 && seq < fseq {
|
||||||
|
fseq = seq
|
||||||
|
}
|
||||||
|
}
|
||||||
|
index += rl
|
||||||
}
|
}
|
||||||
mb.cache.buf = buf
|
mb.cache.buf = buf
|
||||||
mb.cache.idx = idx
|
mb.cache.idx = idx
|
||||||
@@ -4373,6 +4450,7 @@ var (
|
|||||||
errMsgBlkTooBig = errors.New("message block size exceeded int capacity")
|
errMsgBlkTooBig = errors.New("message block size exceeded int capacity")
|
||||||
errUnknownCipher = errors.New("unknown cipher")
|
errUnknownCipher = errors.New("unknown cipher")
|
||||||
errDIOStalled = errors.New("IO is stalled")
|
errDIOStalled = errors.New("IO is stalled")
|
||||||
|
errNoMainKey = errors.New("encrypted store encountered with no main key")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Used for marking messages that have had their checksums checked.
|
// Used for marking messages that have had their checksums checked.
|
||||||
@@ -4382,6 +4460,9 @@ const hbit = 1 << 31
|
|||||||
// Used for marking erased messages sequences.
|
// Used for marking erased messages sequences.
|
||||||
const ebit = 1 << 63
|
const ebit = 1 << 63
|
||||||
|
|
||||||
|
// Used to mark a bad index as deleted.
|
||||||
|
const dbit = 1 << 30
|
||||||
|
|
||||||
// Will do a lookup from cache.
|
// Will do a lookup from cache.
|
||||||
// Lock should be held.
|
// Lock should be held.
|
||||||
func (mb *msgBlock) cacheLookup(seq uint64, sm *StoreMsg) (*StoreMsg, error) {
|
func (mb *msgBlock) cacheLookup(seq uint64, sm *StoreMsg) (*StoreMsg, error) {
|
||||||
@@ -4392,6 +4473,7 @@ func (mb *msgBlock) cacheLookup(seq uint64, sm *StoreMsg) (*StoreMsg, error) {
|
|||||||
// If we have a delete map check it.
|
// If we have a delete map check it.
|
||||||
if mb.dmap != nil {
|
if mb.dmap != nil {
|
||||||
if _, ok := mb.dmap[seq]; ok {
|
if _, ok := mb.dmap[seq]; ok {
|
||||||
|
mb.llts = time.Now().UnixNano()
|
||||||
return nil, errDeletedMsg
|
return nil, errDeletedMsg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4534,9 +4616,9 @@ func (mb *msgBlock) msgFromBuf(buf []byte, sm *StoreMsg, hh hash.Hash64) (*Store
|
|||||||
hh.Write(hdr[4:20])
|
hh.Write(hdr[4:20])
|
||||||
hh.Write(data[:slen])
|
hh.Write(data[:slen])
|
||||||
if hasHeaders {
|
if hasHeaders {
|
||||||
hh.Write(data[slen+4 : dlen-8])
|
hh.Write(data[slen+4 : dlen-recordHashSize])
|
||||||
} else {
|
} else {
|
||||||
hh.Write(data[slen : dlen-8])
|
hh.Write(data[slen : dlen-recordHashSize])
|
||||||
}
|
}
|
||||||
if !bytes.Equal(hh.Sum(nil), data[len(data)-8:]) {
|
if !bytes.Equal(hh.Sum(nil), data[len(data)-8:]) {
|
||||||
return nil, errBadMsg
|
return nil, errBadMsg
|
||||||
@@ -5339,13 +5421,13 @@ func (fs *fileStore) Compact(seq uint64) (uint64, error) {
|
|||||||
|
|
||||||
var purged, bytes uint64
|
var purged, bytes uint64
|
||||||
|
|
||||||
// We have to delete interior messages.
|
|
||||||
fs.mu.Lock()
|
fs.mu.Lock()
|
||||||
|
// Same as purge all.
|
||||||
if lseq := fs.state.LastSeq; seq > lseq {
|
if lseq := fs.state.LastSeq; seq > lseq {
|
||||||
fs.mu.Unlock()
|
fs.mu.Unlock()
|
||||||
return fs.purge(seq)
|
return fs.purge(seq)
|
||||||
}
|
}
|
||||||
|
// We have to delete interior messages.
|
||||||
smb := fs.selectMsgBlock(seq)
|
smb := fs.selectMsgBlock(seq)
|
||||||
if smb == nil {
|
if smb == nil {
|
||||||
fs.mu.Unlock()
|
fs.mu.Unlock()
|
||||||
@@ -5852,6 +5934,8 @@ func (mb *msgBlock) recalculateFirstForSubj(subj string, startSeq uint64, ss *Si
|
|||||||
if startSlot >= len(mb.cache.idx) {
|
if startSlot >= len(mb.cache.idx) {
|
||||||
ss.First = ss.Last
|
ss.First = ss.Last
|
||||||
return
|
return
|
||||||
|
} else if startSlot < 0 {
|
||||||
|
startSlot = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
var le = binary.LittleEndian
|
var le = binary.LittleEndian
|
||||||
@@ -6261,6 +6345,9 @@ func (fs *fileStore) Stop() error {
|
|||||||
fs.cancelSyncTimer()
|
fs.cancelSyncTimer()
|
||||||
fs.cancelAgeChk()
|
fs.cancelAgeChk()
|
||||||
|
|
||||||
|
// We should update the upper usage layer on a stop.
|
||||||
|
cb, bytes := fs.scb, int64(fs.state.Bytes)
|
||||||
|
|
||||||
var _cfs [256]ConsumerStore
|
var _cfs [256]ConsumerStore
|
||||||
cfs := append(_cfs[:0], fs.cfs...)
|
cfs := append(_cfs[:0], fs.cfs...)
|
||||||
fs.cfs = nil
|
fs.cfs = nil
|
||||||
@@ -6270,6 +6357,10 @@ func (fs *fileStore) Stop() error {
|
|||||||
o.Stop()
|
o.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if bytes > 0 && cb != nil {
|
||||||
|
cb(0, -bytes, 0, _EMPTY_)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -2953,7 +2953,7 @@ func (c *client) processInboundGatewayMsg(msg []byte) {
|
|||||||
// Check if this is a service reply subject (_R_)
|
// Check if this is a service reply subject (_R_)
|
||||||
noInterest := len(r.psubs) == 0
|
noInterest := len(r.psubs) == 0
|
||||||
checkNoInterest := true
|
checkNoInterest := true
|
||||||
if acc.imports.services != nil {
|
if acc.NumServiceImports() > 0 {
|
||||||
if isServiceReply(c.pa.subject) {
|
if isServiceReply(c.pa.subject) {
|
||||||
checkNoInterest = false
|
checkNoInterest = false
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+1
-1
@@ -2734,7 +2734,7 @@ func canonicalName(name string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// To throttle the out of resources errors.
|
// To throttle the out of resources errors.
|
||||||
func (s *Server) resourcesExeededError() {
|
func (s *Server) resourcesExceededError() {
|
||||||
var didAlert bool
|
var didAlert bool
|
||||||
|
|
||||||
s.rerrMu.Lock()
|
s.rerrMu.Lock()
|
||||||
|
|||||||
+8
-8
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2020-2022 The NATS Authors
|
// Copyright 2020-2023 The NATS Authors
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
@@ -2656,7 +2656,7 @@ func (s *Server) jsLeaderAccountPurgeRequest(sub *subscription, c *client, _ *Ac
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Request to have the meta leader stepdown.
|
// Request to have the meta leader stepdown.
|
||||||
// These will only be received the meta leaders, so less checking needed.
|
// These will only be received by the meta leader, so less checking needed.
|
||||||
func (s *Server) jsLeaderStepDownRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) {
|
func (s *Server) jsLeaderStepDownRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) {
|
||||||
if c == nil || !s.JetStreamEnabled() {
|
if c == nil || !s.JetStreamEnabled() {
|
||||||
return
|
return
|
||||||
@@ -3360,7 +3360,7 @@ func (s *Server) processStreamRestore(ci *ClientInfo, acc *Account, cfg *StreamC
|
|||||||
// TODO(dlc) - We could check apriori and cancel initial request if we know it won't fit.
|
// TODO(dlc) - We could check apriori and cancel initial request if we know it won't fit.
|
||||||
total += len(msg)
|
total += len(msg)
|
||||||
if js.wouldExceedLimits(FileStorage, total) {
|
if js.wouldExceedLimits(FileStorage, total) {
|
||||||
s.resourcesExeededError()
|
s.resourcesExceededError()
|
||||||
resultCh <- result{NewJSInsufficientResourcesError(), reply}
|
resultCh <- result{NewJSInsufficientResourcesError(), reply}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -3766,11 +3766,11 @@ func (s *Server) jsConsumerCreateRequest(sub *subscription, c *client, a *Accoun
|
|||||||
} else {
|
} else {
|
||||||
streamName = streamNameFromSubject(subject)
|
streamName = streamNameFromSubject(subject)
|
||||||
consumerName = consumerNameFromSubject(subject)
|
consumerName = consumerNameFromSubject(subject)
|
||||||
}
|
// New has optional filtered subject as part of main subject..
|
||||||
// New has optional filtered subject as part of main subject..
|
if n > 6 {
|
||||||
if n > 7 {
|
tokens := strings.Split(subject, tsep)
|
||||||
tokens := strings.Split(subject, tsep)
|
filteredSubject = strings.Join(tokens[6:], tsep)
|
||||||
filteredSubject = strings.Join(tokens[6:], tsep)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+22
-15
@@ -759,6 +759,7 @@ func (js *jetStream) setupMetaGroup() error {
|
|||||||
s.Errorf("Error creating filestore: %v", err)
|
s.Errorf("Error creating filestore: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register our server.
|
// Register our server.
|
||||||
fs.registerServer(s)
|
fs.registerServer(s)
|
||||||
|
|
||||||
@@ -2290,9 +2291,13 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps
|
|||||||
|
|
||||||
case isLeader = <-lch:
|
case isLeader = <-lch:
|
||||||
if isLeader {
|
if isLeader {
|
||||||
if sendSnapshot && mset != nil && n != nil {
|
if mset != nil && n != nil {
|
||||||
n.SendSnapshot(mset.stateSnapshot())
|
// Send a snapshot if being asked or if we are tracking
|
||||||
sendSnapshot = false
|
// a failed state so that followers sync.
|
||||||
|
if clfs := mset.clearCLFS(); clfs > 0 || sendSnapshot {
|
||||||
|
n.SendSnapshot(mset.stateSnapshot())
|
||||||
|
sendSnapshot = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if isRestore {
|
if isRestore {
|
||||||
acc, _ := s.LookupAccount(sa.Client.serviceAccount())
|
acc, _ := s.LookupAccount(sa.Client.serviceAccount())
|
||||||
@@ -2713,15 +2718,14 @@ func (js *jetStream) applyStreamEntries(mset *stream, ce *CommittedEntry, isReco
|
|||||||
|
|
||||||
// Grab last sequence and CLFS.
|
// Grab last sequence and CLFS.
|
||||||
last, clfs := mset.lastSeqAndCLFS()
|
last, clfs := mset.lastSeqAndCLFS()
|
||||||
|
|
||||||
// We can skip if we know this is less than what we already have.
|
// We can skip if we know this is less than what we already have.
|
||||||
if lseq-clfs < last {
|
if lseq-clfs < last {
|
||||||
s.Debugf("Apply stream entries for '%s > %s' skipping message with sequence %d with last of %d",
|
s.Debugf("Apply stream entries for '%s > %s' skipping message with sequence %d with last of %d",
|
||||||
mset.account(), mset.name(), lseq+1-clfs, last)
|
mset.account(), mset.name(), lseq+1-clfs, last)
|
||||||
// Check for any preAcks in case we are interest based.
|
|
||||||
mset.mu.Lock()
|
mset.mu.Lock()
|
||||||
seq := lseq + 1 - mset.clfs
|
// Check for any preAcks in case we are interest based.
|
||||||
mset.clearAllPreAcks(seq)
|
mset.clearAllPreAcks(lseq + 1 - mset.clfs)
|
||||||
mset.mu.Unlock()
|
mset.mu.Unlock()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -2807,12 +2811,15 @@ func (js *jetStream) applyStreamEntries(mset *stream, ce *CommittedEntry, isReco
|
|||||||
}
|
}
|
||||||
panic(err.Error())
|
panic(err.Error())
|
||||||
}
|
}
|
||||||
// Ignore if we are recovering and we have already processed.
|
// If no explicit request, fill in with leader stamped last sequence to protect ourselves on replay during server start.
|
||||||
if isRecovering && (sp.Request == nil || sp.Request.Sequence == 0) {
|
if sp.Request == nil || sp.Request.Sequence == 0 {
|
||||||
|
purgeSeq := sp.LastSeq + 1
|
||||||
if sp.Request == nil {
|
if sp.Request == nil {
|
||||||
sp.Request = &JSApiStreamPurgeRequest{Sequence: sp.LastSeq}
|
sp.Request = &JSApiStreamPurgeRequest{Sequence: purgeSeq}
|
||||||
} else {
|
} else if sp.Request.Keep == 0 {
|
||||||
sp.Request.Sequence = sp.LastSeq
|
sp.Request.Sequence = purgeSeq
|
||||||
|
} else if isRecovering {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6523,7 +6530,7 @@ LOOP:
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.Total = len(resp.Consumers)
|
resp.Total = ocnt
|
||||||
resp.Limit = JSApiListLimit
|
resp.Limit = JSApiListLimit
|
||||||
resp.Offset = offset
|
resp.Offset = offset
|
||||||
resp.Missing = missingNames
|
resp.Missing = missingNames
|
||||||
@@ -7222,7 +7229,7 @@ func (mset *stream) processClusteredInboundMsg(subject, reply string, hdr, msg [
|
|||||||
|
|
||||||
// Check here pre-emptively if we have exceeded this server limits.
|
// Check here pre-emptively if we have exceeded this server limits.
|
||||||
if js.limitsExceeded(stype) {
|
if js.limitsExceeded(stype) {
|
||||||
s.resourcesExeededError()
|
s.resourcesExceededError()
|
||||||
if canRespond {
|
if canRespond {
|
||||||
b, _ := json.Marshal(&JSPubAckResponse{PubAck: &PubAck{Stream: name}, Error: NewJSInsufficientResourcesError()})
|
b, _ := json.Marshal(&JSPubAckResponse{PubAck: &PubAck{Stream: name}, Error: NewJSInsufficientResourcesError()})
|
||||||
outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, nil, b, nil, 0))
|
outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, nil, b, nil, 0))
|
||||||
@@ -7768,7 +7775,7 @@ RETRY:
|
|||||||
} else if err == NewJSInsufficientResourcesError() {
|
} else if err == NewJSInsufficientResourcesError() {
|
||||||
notifyLeaderStopCatchup(mrec, err)
|
notifyLeaderStopCatchup(mrec, err)
|
||||||
if mset.js.limitsExceeded(mset.cfg.Storage) {
|
if mset.js.limitsExceeded(mset.cfg.Storage) {
|
||||||
s.resourcesExeededError()
|
s.resourcesExceededError()
|
||||||
} else {
|
} else {
|
||||||
s.Warnf("Catchup for stream '%s > %s' errored, account resources exceeded: %v", mset.account(), mset.name(), err)
|
s.Warnf("Catchup for stream '%s > %s' errored, account resources exceeded: %v", mset.account(), mset.name(), err)
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-2
@@ -2101,8 +2101,11 @@ func (c *client) processLeafSub(argo []byte) (err error) {
|
|||||||
spoke := c.isSpokeLeafNode()
|
spoke := c.isSpokeLeafNode()
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
|
|
||||||
if err := c.addShadowSubscriptions(acc, sub); err != nil {
|
// Only add in shadow subs if a new sub or qsub.
|
||||||
c.Errorf(err.Error())
|
if osub == nil {
|
||||||
|
if err := c.addShadowSubscriptions(acc, sub); err != nil {
|
||||||
|
c.Errorf(err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we are not solicited, treat leaf node subscriptions similar to a
|
// If we are not solicited, treat leaf node subscriptions similar to a
|
||||||
|
|||||||
+7
-4
@@ -66,7 +66,7 @@ func (s *Server) ConfigureLogger() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if opts.LogFile != "" {
|
if opts.LogFile != "" {
|
||||||
log = srvlog.NewFileLogger(opts.LogFile, opts.Logtime, opts.Debug, opts.Trace, true)
|
log = srvlog.NewFileLogger(opts.LogFile, opts.Logtime, opts.Debug, opts.Trace, true, srvlog.LogUTC(opts.LogtimeUTC))
|
||||||
if opts.LogSizeLimit > 0 {
|
if opts.LogSizeLimit > 0 {
|
||||||
if l, ok := log.(*srvlog.Logger); ok {
|
if l, ok := log.(*srvlog.Logger); ok {
|
||||||
l.SetSizeLimit(opts.LogSizeLimit)
|
l.SetSizeLimit(opts.LogSizeLimit)
|
||||||
@@ -84,7 +84,7 @@ func (s *Server) ConfigureLogger() {
|
|||||||
if err != nil || (stat.Mode()&os.ModeCharDevice) == 0 {
|
if err != nil || (stat.Mode()&os.ModeCharDevice) == 0 {
|
||||||
colors = false
|
colors = false
|
||||||
}
|
}
|
||||||
log = srvlog.NewStdLogger(opts.Logtime, opts.Debug, opts.Trace, colors, true)
|
log = srvlog.NewStdLogger(opts.Logtime, opts.Debug, opts.Trace, colors, true, srvlog.LogUTC(opts.LogtimeUTC))
|
||||||
}
|
}
|
||||||
|
|
||||||
s.SetLoggerV2(log, opts.Debug, opts.Trace, opts.TraceVerbose)
|
s.SetLoggerV2(log, opts.Debug, opts.Trace, opts.TraceVerbose)
|
||||||
@@ -154,8 +154,11 @@ func (s *Server) ReOpenLogFile() {
|
|||||||
if opts.LogFile == "" {
|
if opts.LogFile == "" {
|
||||||
s.Noticef("File log re-open ignored, not a file logger")
|
s.Noticef("File log re-open ignored, not a file logger")
|
||||||
} else {
|
} else {
|
||||||
fileLog := srvlog.NewFileLogger(opts.LogFile,
|
fileLog := srvlog.NewFileLogger(
|
||||||
opts.Logtime, opts.Debug, opts.Trace, true)
|
opts.LogFile, opts.Logtime,
|
||||||
|
opts.Debug, opts.Trace, true,
|
||||||
|
srvlog.LogUTC(opts.LogtimeUTC),
|
||||||
|
)
|
||||||
s.SetLogger(fileLog, opts.Debug, opts.Trace)
|
s.SetLogger(fileLog, opts.Debug, opts.Trace)
|
||||||
if opts.LogSizeLimit > 0 {
|
if opts.LogSizeLimit > 0 {
|
||||||
fileLog.SetSizeLimit(opts.LogSizeLimit)
|
fileLog.SetSizeLimit(opts.LogSizeLimit)
|
||||||
|
|||||||
+2
-1
@@ -1144,11 +1144,12 @@ func memStoreMsgSize(subj string, hdr, msg []byte) uint64 {
|
|||||||
|
|
||||||
// Delete is same as Stop for memory store.
|
// Delete is same as Stop for memory store.
|
||||||
func (ms *memStore) Delete() error {
|
func (ms *memStore) Delete() error {
|
||||||
ms.Purge()
|
|
||||||
return ms.Stop()
|
return ms.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ms *memStore) Stop() error {
|
func (ms *memStore) Stop() error {
|
||||||
|
// These can't come back, so stop is same as Delete.
|
||||||
|
ms.Purge()
|
||||||
ms.mu.Lock()
|
ms.mu.Lock()
|
||||||
if ms.ageChk != nil {
|
if ms.ageChk != nil {
|
||||||
ms.ageChk.Stop()
|
ms.ageChk.Stop()
|
||||||
|
|||||||
+111
-67
@@ -1154,6 +1154,7 @@ type Varz struct {
|
|||||||
AuthRequired bool `json:"auth_required,omitempty"`
|
AuthRequired bool `json:"auth_required,omitempty"`
|
||||||
TLSRequired bool `json:"tls_required,omitempty"`
|
TLSRequired bool `json:"tls_required,omitempty"`
|
||||||
TLSVerify bool `json:"tls_verify,omitempty"`
|
TLSVerify bool `json:"tls_verify,omitempty"`
|
||||||
|
TLSOCSPPeerVerify bool `json:"tls_ocsp_peer_verify,omitempty"`
|
||||||
IP string `json:"ip,omitempty"`
|
IP string `json:"ip,omitempty"`
|
||||||
ClientConnectURLs []string `json:"connect_urls,omitempty"`
|
ClientConnectURLs []string `json:"connect_urls,omitempty"`
|
||||||
WSConnectURLs []string `json:"ws_connect_urls,omitempty"`
|
WSConnectURLs []string `json:"ws_connect_urls,omitempty"`
|
||||||
@@ -1202,6 +1203,7 @@ type Varz struct {
|
|||||||
TrustedOperatorsClaim []*jwt.OperatorClaims `json:"trusted_operators_claim,omitempty"`
|
TrustedOperatorsClaim []*jwt.OperatorClaims `json:"trusted_operators_claim,omitempty"`
|
||||||
SystemAccount string `json:"system_account,omitempty"`
|
SystemAccount string `json:"system_account,omitempty"`
|
||||||
PinnedAccountFail uint64 `json:"pinned_account_fails,omitempty"`
|
PinnedAccountFail uint64 `json:"pinned_account_fails,omitempty"`
|
||||||
|
OCSPResponseCache OCSPResponseCacheVarz `json:"ocsp_peer_cache,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// JetStreamVarz contains basic runtime information about jetstream
|
// JetStreamVarz contains basic runtime information about jetstream
|
||||||
@@ -1247,13 +1249,14 @@ type RemoteGatewayOptsVarz struct {
|
|||||||
|
|
||||||
// LeafNodeOptsVarz contains monitoring leaf node information
|
// LeafNodeOptsVarz contains monitoring leaf node information
|
||||||
type LeafNodeOptsVarz struct {
|
type LeafNodeOptsVarz struct {
|
||||||
Host string `json:"host,omitempty"`
|
Host string `json:"host,omitempty"`
|
||||||
Port int `json:"port,omitempty"`
|
Port int `json:"port,omitempty"`
|
||||||
AuthTimeout float64 `json:"auth_timeout,omitempty"`
|
AuthTimeout float64 `json:"auth_timeout,omitempty"`
|
||||||
TLSTimeout float64 `json:"tls_timeout,omitempty"`
|
TLSTimeout float64 `json:"tls_timeout,omitempty"`
|
||||||
TLSRequired bool `json:"tls_required,omitempty"`
|
TLSRequired bool `json:"tls_required,omitempty"`
|
||||||
TLSVerify bool `json:"tls_verify,omitempty"`
|
TLSVerify bool `json:"tls_verify,omitempty"`
|
||||||
Remotes []RemoteLeafOptsVarz `json:"remotes,omitempty"`
|
Remotes []RemoteLeafOptsVarz `json:"remotes,omitempty"`
|
||||||
|
TLSOCSPPeerVerify bool `json:"tls_ocsp_peer_verify,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DenyRules Contains lists of subjects not allowed to be imported/exported
|
// DenyRules Contains lists of subjects not allowed to be imported/exported
|
||||||
@@ -1264,41 +1267,55 @@ type DenyRules struct {
|
|||||||
|
|
||||||
// RemoteLeafOptsVarz contains monitoring remote leaf node information
|
// RemoteLeafOptsVarz contains monitoring remote leaf node information
|
||||||
type RemoteLeafOptsVarz struct {
|
type RemoteLeafOptsVarz struct {
|
||||||
LocalAccount string `json:"local_account,omitempty"`
|
LocalAccount string `json:"local_account,omitempty"`
|
||||||
TLSTimeout float64 `json:"tls_timeout,omitempty"`
|
TLSTimeout float64 `json:"tls_timeout,omitempty"`
|
||||||
URLs []string `json:"urls,omitempty"`
|
URLs []string `json:"urls,omitempty"`
|
||||||
Deny *DenyRules `json:"deny,omitempty"`
|
Deny *DenyRules `json:"deny,omitempty"`
|
||||||
|
TLSOCSPPeerVerify bool `json:"tls_ocsp_peer_verify,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MQTTOptsVarz contains monitoring MQTT information
|
// MQTTOptsVarz contains monitoring MQTT information
|
||||||
type MQTTOptsVarz struct {
|
type MQTTOptsVarz struct {
|
||||||
Host string `json:"host,omitempty"`
|
Host string `json:"host,omitempty"`
|
||||||
Port int `json:"port,omitempty"`
|
Port int `json:"port,omitempty"`
|
||||||
NoAuthUser string `json:"no_auth_user,omitempty"`
|
NoAuthUser string `json:"no_auth_user,omitempty"`
|
||||||
AuthTimeout float64 `json:"auth_timeout,omitempty"`
|
AuthTimeout float64 `json:"auth_timeout,omitempty"`
|
||||||
TLSMap bool `json:"tls_map,omitempty"`
|
TLSMap bool `json:"tls_map,omitempty"`
|
||||||
TLSTimeout float64 `json:"tls_timeout,omitempty"`
|
TLSTimeout float64 `json:"tls_timeout,omitempty"`
|
||||||
TLSPinnedCerts []string `json:"tls_pinned_certs,omitempty"`
|
TLSPinnedCerts []string `json:"tls_pinned_certs,omitempty"`
|
||||||
JsDomain string `json:"js_domain,omitempty"`
|
JsDomain string `json:"js_domain,omitempty"`
|
||||||
AckWait time.Duration `json:"ack_wait,omitempty"`
|
AckWait time.Duration `json:"ack_wait,omitempty"`
|
||||||
MaxAckPending uint16 `json:"max_ack_pending,omitempty"`
|
MaxAckPending uint16 `json:"max_ack_pending,omitempty"`
|
||||||
|
TLSOCSPPeerVerify bool `json:"tls_ocsp_peer_verify,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebsocketOptsVarz contains monitoring websocket information
|
// WebsocketOptsVarz contains monitoring websocket information
|
||||||
type WebsocketOptsVarz struct {
|
type WebsocketOptsVarz struct {
|
||||||
Host string `json:"host,omitempty"`
|
Host string `json:"host,omitempty"`
|
||||||
Port int `json:"port,omitempty"`
|
Port int `json:"port,omitempty"`
|
||||||
Advertise string `json:"advertise,omitempty"`
|
Advertise string `json:"advertise,omitempty"`
|
||||||
NoAuthUser string `json:"no_auth_user,omitempty"`
|
NoAuthUser string `json:"no_auth_user,omitempty"`
|
||||||
JWTCookie string `json:"jwt_cookie,omitempty"`
|
JWTCookie string `json:"jwt_cookie,omitempty"`
|
||||||
HandshakeTimeout time.Duration `json:"handshake_timeout,omitempty"`
|
HandshakeTimeout time.Duration `json:"handshake_timeout,omitempty"`
|
||||||
AuthTimeout float64 `json:"auth_timeout,omitempty"`
|
AuthTimeout float64 `json:"auth_timeout,omitempty"`
|
||||||
NoTLS bool `json:"no_tls,omitempty"`
|
NoTLS bool `json:"no_tls,omitempty"`
|
||||||
TLSMap bool `json:"tls_map,omitempty"`
|
TLSMap bool `json:"tls_map,omitempty"`
|
||||||
TLSPinnedCerts []string `json:"tls_pinned_certs,omitempty"`
|
TLSPinnedCerts []string `json:"tls_pinned_certs,omitempty"`
|
||||||
SameOrigin bool `json:"same_origin,omitempty"`
|
SameOrigin bool `json:"same_origin,omitempty"`
|
||||||
AllowedOrigins []string `json:"allowed_origins,omitempty"`
|
AllowedOrigins []string `json:"allowed_origins,omitempty"`
|
||||||
Compression bool `json:"compression,omitempty"`
|
Compression bool `json:"compression,omitempty"`
|
||||||
|
TLSOCSPPeerVerify bool `json:"tls_ocsp_peer_verify,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// OCSPResponseCacheVarz contains OCSP response cache information
|
||||||
|
type OCSPResponseCacheVarz struct {
|
||||||
|
Type string `json:"cache_type,omitempty"`
|
||||||
|
Hits int64 `json:"cache_hits,omitempty"`
|
||||||
|
Misses int64 `json:"cache_misses,omitempty"`
|
||||||
|
Responses int64 `json:"cached_responses,omitempty"`
|
||||||
|
Revokes int64 `json:"cached_revoked_responses,omitempty"`
|
||||||
|
Goods int64 `json:"cached_good_responses,omitempty"`
|
||||||
|
Unknowns int64 `json:"cached_unknown_responses,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// VarzOptions are the options passed to Varz().
|
// VarzOptions are the options passed to Varz().
|
||||||
@@ -1452,6 +1469,9 @@ func (s *Server) createVarz(pcpu float64, rss int64) *Varz {
|
|||||||
gatewayTlsReq := gw.TLSConfig != nil
|
gatewayTlsReq := gw.TLSConfig != nil
|
||||||
leafTlsReq := ln.TLSConfig != nil
|
leafTlsReq := ln.TLSConfig != nil
|
||||||
leafTlsVerify := leafTlsReq && ln.TLSConfig.ClientAuth == tls.RequireAndVerifyClientCert
|
leafTlsVerify := leafTlsReq && ln.TLSConfig.ClientAuth == tls.RequireAndVerifyClientCert
|
||||||
|
leafTlsOCSPPeerVerify := s.ocspPeerVerify && leafTlsReq && ln.tlsConfigOpts.OCSPPeerConfig != nil && ln.tlsConfigOpts.OCSPPeerConfig.Verify
|
||||||
|
mqttTlsOCSPPeerVerify := s.ocspPeerVerify && mqtt.TLSConfig != nil && mqtt.tlsConfigOpts.OCSPPeerConfig != nil && mqtt.tlsConfigOpts.OCSPPeerConfig.Verify
|
||||||
|
wsTlsOCSPPeerVerify := s.ocspPeerVerify && ws.TLSConfig != nil && ws.tlsConfigOpts.OCSPPeerConfig != nil && ws.tlsConfigOpts.OCSPPeerConfig.Verify
|
||||||
varz := &Varz{
|
varz := &Varz{
|
||||||
ID: info.ID,
|
ID: info.ID,
|
||||||
Version: info.Version,
|
Version: info.Version,
|
||||||
@@ -1489,38 +1509,41 @@ func (s *Server) createVarz(pcpu float64, rss int64) *Varz {
|
|||||||
RejectUnknown: gw.RejectUnknown,
|
RejectUnknown: gw.RejectUnknown,
|
||||||
},
|
},
|
||||||
LeafNode: LeafNodeOptsVarz{
|
LeafNode: LeafNodeOptsVarz{
|
||||||
Host: ln.Host,
|
Host: ln.Host,
|
||||||
Port: ln.Port,
|
Port: ln.Port,
|
||||||
AuthTimeout: ln.AuthTimeout,
|
AuthTimeout: ln.AuthTimeout,
|
||||||
TLSTimeout: ln.TLSTimeout,
|
TLSTimeout: ln.TLSTimeout,
|
||||||
TLSRequired: leafTlsReq,
|
TLSRequired: leafTlsReq,
|
||||||
TLSVerify: leafTlsVerify,
|
TLSVerify: leafTlsVerify,
|
||||||
Remotes: []RemoteLeafOptsVarz{},
|
TLSOCSPPeerVerify: leafTlsOCSPPeerVerify,
|
||||||
|
Remotes: []RemoteLeafOptsVarz{},
|
||||||
},
|
},
|
||||||
MQTT: MQTTOptsVarz{
|
MQTT: MQTTOptsVarz{
|
||||||
Host: mqtt.Host,
|
Host: mqtt.Host,
|
||||||
Port: mqtt.Port,
|
Port: mqtt.Port,
|
||||||
NoAuthUser: mqtt.NoAuthUser,
|
NoAuthUser: mqtt.NoAuthUser,
|
||||||
AuthTimeout: mqtt.AuthTimeout,
|
AuthTimeout: mqtt.AuthTimeout,
|
||||||
TLSMap: mqtt.TLSMap,
|
TLSMap: mqtt.TLSMap,
|
||||||
TLSTimeout: mqtt.TLSTimeout,
|
TLSTimeout: mqtt.TLSTimeout,
|
||||||
JsDomain: mqtt.JsDomain,
|
JsDomain: mqtt.JsDomain,
|
||||||
AckWait: mqtt.AckWait,
|
AckWait: mqtt.AckWait,
|
||||||
MaxAckPending: mqtt.MaxAckPending,
|
MaxAckPending: mqtt.MaxAckPending,
|
||||||
|
TLSOCSPPeerVerify: mqttTlsOCSPPeerVerify,
|
||||||
},
|
},
|
||||||
Websocket: WebsocketOptsVarz{
|
Websocket: WebsocketOptsVarz{
|
||||||
Host: ws.Host,
|
Host: ws.Host,
|
||||||
Port: ws.Port,
|
Port: ws.Port,
|
||||||
Advertise: ws.Advertise,
|
Advertise: ws.Advertise,
|
||||||
NoAuthUser: ws.NoAuthUser,
|
NoAuthUser: ws.NoAuthUser,
|
||||||
JWTCookie: ws.JWTCookie,
|
JWTCookie: ws.JWTCookie,
|
||||||
AuthTimeout: ws.AuthTimeout,
|
AuthTimeout: ws.AuthTimeout,
|
||||||
NoTLS: ws.NoTLS,
|
NoTLS: ws.NoTLS,
|
||||||
TLSMap: ws.TLSMap,
|
TLSMap: ws.TLSMap,
|
||||||
SameOrigin: ws.SameOrigin,
|
SameOrigin: ws.SameOrigin,
|
||||||
AllowedOrigins: copyStrings(ws.AllowedOrigins),
|
AllowedOrigins: copyStrings(ws.AllowedOrigins),
|
||||||
Compression: ws.Compression,
|
Compression: ws.Compression,
|
||||||
HandshakeTimeout: ws.HandshakeTimeout,
|
HandshakeTimeout: ws.HandshakeTimeout,
|
||||||
|
TLSOCSPPeerVerify: wsTlsOCSPPeerVerify,
|
||||||
},
|
},
|
||||||
Start: s.start.UTC(),
|
Start: s.start.UTC(),
|
||||||
MaxSubs: opts.MaxSubs,
|
MaxSubs: opts.MaxSubs,
|
||||||
@@ -1553,11 +1576,14 @@ func (s *Server) createVarz(pcpu float64, rss int64) *Varz {
|
|||||||
Exports: r.DenyExports,
|
Exports: r.DenyExports,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
remoteTlsOCSPPeerVerify := s.ocspPeerVerify && r.tlsConfigOpts != nil && r.tlsConfigOpts.OCSPPeerConfig != nil && r.tlsConfigOpts.OCSPPeerConfig.Verify
|
||||||
|
|
||||||
rlna[i] = RemoteLeafOptsVarz{
|
rlna[i] = RemoteLeafOptsVarz{
|
||||||
LocalAccount: r.LocalAccount,
|
LocalAccount: r.LocalAccount,
|
||||||
URLs: urlsToStrings(r.URLs),
|
URLs: urlsToStrings(r.URLs),
|
||||||
TLSTimeout: r.TLSTimeout,
|
TLSTimeout: r.TLSTimeout,
|
||||||
Deny: deny,
|
Deny: deny,
|
||||||
|
TLSOCSPPeerVerify: remoteTlsOCSPPeerVerify,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
varz.LeafNode.Remotes = rlna
|
varz.LeafNode.Remotes = rlna
|
||||||
@@ -1611,6 +1637,8 @@ func (s *Server) updateVarzConfigReloadableFields(v *Varz) {
|
|||||||
}
|
}
|
||||||
v.MQTT.TLSPinnedCerts = getPinnedCertsAsSlice(opts.MQTT.TLSPinnedCerts)
|
v.MQTT.TLSPinnedCerts = getPinnedCertsAsSlice(opts.MQTT.TLSPinnedCerts)
|
||||||
v.Websocket.TLSPinnedCerts = getPinnedCertsAsSlice(opts.Websocket.TLSPinnedCerts)
|
v.Websocket.TLSPinnedCerts = getPinnedCertsAsSlice(opts.Websocket.TLSPinnedCerts)
|
||||||
|
|
||||||
|
v.TLSOCSPPeerVerify = s.ocspPeerVerify && v.TLSRequired && s.opts.tlsConfigOpts != nil && s.opts.tlsConfigOpts.OCSPPeerConfig != nil && s.opts.tlsConfigOpts.OCSPPeerConfig.Verify
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPinnedCertsAsSlice(certs PinnedCertSet) []string {
|
func getPinnedCertsAsSlice(certs PinnedCertSet) []string {
|
||||||
@@ -1702,6 +1730,21 @@ func (s *Server) updateVarzRuntimeFields(v *Varz, forceUpdate bool, pcpu float64
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
gw.RUnlock()
|
gw.RUnlock()
|
||||||
|
|
||||||
|
if s.ocsprc != nil && s.ocsprc.Type() != "none" {
|
||||||
|
stats := s.ocsprc.Stats()
|
||||||
|
if stats != nil {
|
||||||
|
v.OCSPResponseCache = OCSPResponseCacheVarz{
|
||||||
|
s.ocsprc.Type(),
|
||||||
|
stats.Hits,
|
||||||
|
stats.Misses,
|
||||||
|
stats.Responses,
|
||||||
|
stats.Revokes,
|
||||||
|
stats.Goods,
|
||||||
|
stats.Unknowns,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleVarz will process HTTP requests for server information.
|
// HandleVarz will process HTTP requests for server information.
|
||||||
@@ -3147,7 +3190,8 @@ func (s *Server) healthz(opts *HealthzOptions) *HealthStatus {
|
|||||||
for acc, asa := range cc.streams {
|
for acc, asa := range cc.streams {
|
||||||
nasa := make(map[string]*streamAssignment)
|
nasa := make(map[string]*streamAssignment)
|
||||||
for stream, sa := range asa {
|
for stream, sa := range asa {
|
||||||
if sa.Group.isMember(ourID) {
|
// If we are a member and we are not being restored, select for check.
|
||||||
|
if sa.Group.isMember(ourID) && sa.Restore == nil {
|
||||||
csa := sa.copyGroup()
|
csa := sa.copyGroup()
|
||||||
csa.consumers = make(map[string]*consumerAssignment)
|
csa.consumers = make(map[string]*consumerAssignment)
|
||||||
for consumer, ca := range sa.consumers {
|
for consumer, ca := range sa.consumers {
|
||||||
|
|||||||
+3
-3
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2020-2021 The NATS Authors
|
// Copyright 2020-2023 The NATS Authors
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
@@ -3407,8 +3407,8 @@ func mqttSubscribeTrace(pi uint16, filters []*mqttFilter) string {
|
|||||||
// message and this is the callback for a QoS1 subscription because in
|
// message and this is the callback for a QoS1 subscription because in
|
||||||
// that case, it will be handled by the other callback. This avoid getting
|
// that case, it will be handled by the other callback. This avoid getting
|
||||||
// duplicate deliveries.
|
// duplicate deliveries.
|
||||||
func mqttDeliverMsgCbQos0(sub *subscription, pc *client, _ *Account, subject, _ string, rmsg []byte) {
|
func mqttDeliverMsgCbQos0(sub *subscription, pc *client, _ *Account, subject, reply string, rmsg []byte) {
|
||||||
if pc.kind == JETSTREAM {
|
if pc.kind == JETSTREAM && len(reply) > 0 && strings.HasPrefix(reply, jsAckPre) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+188
-83
@@ -30,6 +30,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/crypto/ocsp"
|
"golang.org/x/crypto/ocsp"
|
||||||
|
|
||||||
|
"github.com/nats-io/nats-server/v2/server/certidp"
|
||||||
|
"github.com/nats-io/nats-server/v2/server/certstore"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -389,7 +392,7 @@ func (srv *Server) NewOCSPMonitor(config *tlsConfigKind) (*tls.Config, *OCSPMoni
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add OCSP 'responder_cert' option in case CA cert not available.
|
// TODO: Add OCSP 'responder_cert' option in case CA cert not available.
|
||||||
issuers, err := getOCSPIssuer(caFile, cert.Certificate)
|
issuer, err := getOCSPIssuer(caFile, cert.Certificate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -402,7 +405,7 @@ func (srv *Server) NewOCSPMonitor(config *tlsConfigKind) (*tls.Config, *OCSPMoni
|
|||||||
certFile: certFile,
|
certFile: certFile,
|
||||||
stopCh: make(chan struct{}, 1),
|
stopCh: make(chan struct{}, 1),
|
||||||
Leaf: cert.Leaf,
|
Leaf: cert.Leaf,
|
||||||
Issuer: issuers[len(issuers)-1],
|
Issuer: issuer,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the certificate status from the memory, then remote OCSP responder.
|
// Get the certificate status from the memory, then remote OCSP responder.
|
||||||
@@ -448,21 +451,20 @@ func (srv *Server) NewOCSPMonitor(config *tlsConfigKind) (*tls.Config, *OCSPMoni
|
|||||||
}
|
}
|
||||||
|
|
||||||
chain := s.VerifiedChains[0]
|
chain := s.VerifiedChains[0]
|
||||||
leaf := chain[0]
|
peerLeaf := chain[0]
|
||||||
parent := issuers[len(issuers)-1]
|
peerIssuer := certidp.GetLeafIssuerCert(chain, 0)
|
||||||
|
if peerIssuer == nil {
|
||||||
|
return fmt.Errorf("failed to get issuer certificate for %s peer", kind)
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := ocsp.ParseResponseForCert(oresp, leaf, parent)
|
// Response signature of issuer or issuer delegate is checked in the library parse
|
||||||
|
resp, err := ocsp.ParseResponseForCert(oresp, peerLeaf, peerIssuer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to parse OCSP response from %s peer: %w", kind, err)
|
return fmt.Errorf("failed to parse OCSP response from %s peer: %w", kind, err)
|
||||||
}
|
}
|
||||||
if resp.Certificate == nil {
|
|
||||||
if err := resp.CheckSignatureFrom(parent); err != nil {
|
// If signer was issuer delegate double-check issuer delegate authorization
|
||||||
return fmt.Errorf("OCSP staple not issued by issuer: %w", err)
|
if resp.Certificate != nil {
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := resp.Certificate.CheckSignatureFrom(parent); err != nil {
|
|
||||||
return fmt.Errorf("OCSP staple's signer not signed by issuer: %w", err)
|
|
||||||
}
|
|
||||||
ok := false
|
ok := false
|
||||||
for _, eku := range resp.Certificate.ExtKeyUsage {
|
for _, eku := range resp.Certificate.ExtKeyUsage {
|
||||||
if eku == x509.ExtKeyUsageOCSPSigning {
|
if eku == x509.ExtKeyUsageOCSPSigning {
|
||||||
@@ -474,6 +476,14 @@ func (srv *Server) NewOCSPMonitor(config *tlsConfigKind) (*tls.Config, *OCSPMoni
|
|||||||
return fmt.Errorf("OCSP staple's signer missing authorization by CA to act as OCSP signer")
|
return fmt.Errorf("OCSP staple's signer missing authorization by CA to act as OCSP signer")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check that the OCSP response is effective, take defaults for clockskew and default validity
|
||||||
|
peerOpts := certidp.OCSPPeerConfig{ClockSkew: -1, TTLUnsetNextUpdate: -1}
|
||||||
|
sLog := certidp.Log{Debugf: srv.Debugf}
|
||||||
|
if !certidp.OCSPResponseCurrent(resp, &peerOpts, &sLog) {
|
||||||
|
return fmt.Errorf("OCSP staple from %s peer not current", kind)
|
||||||
|
}
|
||||||
|
|
||||||
if resp.Status != ocsp.Good {
|
if resp.Status != ocsp.Good {
|
||||||
return fmt.Errorf("bad status for OCSP Staple from %s peer: %s", kind, ocspStatusString(resp.Status))
|
return fmt.Errorf("bad status for OCSP Staple from %s peer: %s", kind, ocspStatusString(resp.Status))
|
||||||
}
|
}
|
||||||
@@ -520,10 +530,11 @@ func (s *Server) setupOCSPStapleStoreDir() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type tlsConfigKind struct {
|
type tlsConfigKind struct {
|
||||||
tlsConfig *tls.Config
|
tlsConfig *tls.Config
|
||||||
tlsOpts *TLSConfigOpts
|
tlsOpts *TLSConfigOpts
|
||||||
kind string
|
kind string
|
||||||
apply func(*tls.Config)
|
isLeafSpoke bool
|
||||||
|
apply func(*tls.Config)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) configureOCSP() []*tlsConfigKind {
|
func (s *Server) configureOCSP() []*tlsConfigKind {
|
||||||
@@ -541,6 +552,26 @@ func (s *Server) configureOCSP() []*tlsConfigKind {
|
|||||||
}
|
}
|
||||||
configs = append(configs, o)
|
configs = append(configs, o)
|
||||||
}
|
}
|
||||||
|
if config := sopts.Websocket.TLSConfig; config != nil {
|
||||||
|
opts := sopts.Websocket.tlsConfigOpts
|
||||||
|
o := &tlsConfigKind{
|
||||||
|
kind: kindStringMap[CLIENT],
|
||||||
|
tlsConfig: config,
|
||||||
|
tlsOpts: opts,
|
||||||
|
apply: func(tc *tls.Config) { sopts.Websocket.TLSConfig = tc },
|
||||||
|
}
|
||||||
|
configs = append(configs, o)
|
||||||
|
}
|
||||||
|
if config := sopts.MQTT.TLSConfig; config != nil {
|
||||||
|
opts := sopts.tlsConfigOpts
|
||||||
|
o := &tlsConfigKind{
|
||||||
|
kind: kindStringMap[CLIENT],
|
||||||
|
tlsConfig: config,
|
||||||
|
tlsOpts: opts,
|
||||||
|
apply: func(tc *tls.Config) { sopts.MQTT.TLSConfig = tc },
|
||||||
|
}
|
||||||
|
configs = append(configs, o)
|
||||||
|
}
|
||||||
if config := sopts.Cluster.TLSConfig; config != nil {
|
if config := sopts.Cluster.TLSConfig; config != nil {
|
||||||
opts := sopts.Cluster.tlsConfigOpts
|
opts := sopts.Cluster.tlsConfigOpts
|
||||||
o := &tlsConfigKind{
|
o := &tlsConfigKind{
|
||||||
@@ -557,16 +588,7 @@ func (s *Server) configureOCSP() []*tlsConfigKind {
|
|||||||
kind: kindStringMap[LEAF],
|
kind: kindStringMap[LEAF],
|
||||||
tlsConfig: config,
|
tlsConfig: config,
|
||||||
tlsOpts: opts,
|
tlsOpts: opts,
|
||||||
apply: func(tc *tls.Config) {
|
apply: func(tc *tls.Config) { sopts.LeafNode.TLSConfig = tc },
|
||||||
// RequireAndVerifyClientCert is used to tell a client that it
|
|
||||||
// should send the client cert to the server.
|
|
||||||
if opts.Verify {
|
|
||||||
tc.ClientAuth = tls.RequireAndVerifyClientCert
|
|
||||||
}
|
|
||||||
// We're a leaf hub server, so we must not set this.
|
|
||||||
tc.GetClientCertificate = nil
|
|
||||||
sopts.LeafNode.TLSConfig = tc
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
configs = append(configs, o)
|
configs = append(configs, o)
|
||||||
}
|
}
|
||||||
@@ -576,14 +598,11 @@ func (s *Server) configureOCSP() []*tlsConfigKind {
|
|||||||
// in the apply func callback below.
|
// in the apply func callback below.
|
||||||
r, opts := remote, remote.tlsConfigOpts
|
r, opts := remote, remote.tlsConfigOpts
|
||||||
o := &tlsConfigKind{
|
o := &tlsConfigKind{
|
||||||
kind: kindStringMap[LEAF],
|
kind: kindStringMap[LEAF],
|
||||||
tlsConfig: config,
|
tlsConfig: config,
|
||||||
tlsOpts: opts,
|
tlsOpts: opts,
|
||||||
apply: func(tc *tls.Config) {
|
isLeafSpoke: true,
|
||||||
// We're a leaf client, so we must not set this.
|
apply: func(tc *tls.Config) { r.TLSConfig = tc },
|
||||||
tc.GetCertificate = nil
|
|
||||||
r.TLSConfig = tc
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
configs = append(configs, o)
|
configs = append(configs, o)
|
||||||
}
|
}
|
||||||
@@ -605,9 +624,7 @@ func (s *Server) configureOCSP() []*tlsConfigKind {
|
|||||||
kind: kindStringMap[GATEWAY],
|
kind: kindStringMap[GATEWAY],
|
||||||
tlsConfig: config,
|
tlsConfig: config,
|
||||||
tlsOpts: opts,
|
tlsOpts: opts,
|
||||||
apply: func(tc *tls.Config) {
|
apply: func(tc *tls.Config) { gw.TLSConfig = tc },
|
||||||
gw.TLSConfig = tc
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
configs = append(configs, o)
|
configs = append(configs, o)
|
||||||
}
|
}
|
||||||
@@ -619,16 +636,33 @@ func (s *Server) enableOCSP() error {
|
|||||||
configs := s.configureOCSP()
|
configs := s.configureOCSP()
|
||||||
|
|
||||||
for _, config := range configs {
|
for _, config := range configs {
|
||||||
tc, mon, err := s.NewOCSPMonitor(config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Check if an OCSP stapling monitor is required for this certificate.
|
|
||||||
if mon != nil {
|
|
||||||
s.ocsps = append(s.ocsps, mon)
|
|
||||||
|
|
||||||
// Override the TLS config with one that follows OCSP.
|
// We do not staple Leaf Hub and Leaf Spokes, use ocsp_peer
|
||||||
config.apply(tc)
|
if config.kind != kindStringMap[LEAF] {
|
||||||
|
// OCSP Stapling feature, will also enable tls server peer check for gateway and route peers
|
||||||
|
tc, mon, err := s.NewOCSPMonitor(config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Check if an OCSP stapling monitor is required for this certificate.
|
||||||
|
if mon != nil {
|
||||||
|
s.ocsps = append(s.ocsps, mon)
|
||||||
|
|
||||||
|
// Override the TLS config with one that follows OCSP stapling
|
||||||
|
config.apply(tc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OCSP peer check (client mTLS, leaf mTLS, leaf remote TLS)
|
||||||
|
if config.kind == kindStringMap[CLIENT] || config.kind == kindStringMap[LEAF] {
|
||||||
|
tc, plugged, err := s.plugTLSOCSPPeer(config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if plugged && tc != nil {
|
||||||
|
s.ocspPeerVerify = true
|
||||||
|
config.apply(tc)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -670,17 +704,39 @@ func (s *Server) reloadOCSP() error {
|
|||||||
|
|
||||||
// Restart the monitors under the new configuration.
|
// Restart the monitors under the new configuration.
|
||||||
ocspm := make([]*OCSPMonitor, 0)
|
ocspm := make([]*OCSPMonitor, 0)
|
||||||
for _, config := range configs {
|
|
||||||
tc, mon, err := s.NewOCSPMonitor(config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Check if an OCSP stapling monitor is required for this certificate.
|
|
||||||
if mon != nil {
|
|
||||||
ocspm = append(ocspm, mon)
|
|
||||||
|
|
||||||
// Apply latest TLS configuration.
|
// Reset server's ocspPeerVerify flag to re-detect at least one plugged OCSP peer
|
||||||
config.apply(tc)
|
s.mu.Lock()
|
||||||
|
s.ocspPeerVerify = false
|
||||||
|
s.mu.Unlock()
|
||||||
|
s.stopOCSPResponseCache()
|
||||||
|
|
||||||
|
for _, config := range configs {
|
||||||
|
// We do not staple Leaf Hub and Leaf Spokes, use ocsp_peer
|
||||||
|
if config.kind != kindStringMap[LEAF] {
|
||||||
|
tc, mon, err := s.NewOCSPMonitor(config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Check if an OCSP stapling monitor is required for this certificate.
|
||||||
|
if mon != nil {
|
||||||
|
ocspm = append(ocspm, mon)
|
||||||
|
|
||||||
|
// Apply latest TLS configuration.
|
||||||
|
config.apply(tc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OCSP peer check (client mTLS, leaf mTLS, leaf remote TLS)
|
||||||
|
if config.kind == kindStringMap[CLIENT] || config.kind == kindStringMap[LEAF] {
|
||||||
|
tc, plugged, err := s.plugTLSOCSPPeer(config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if plugged && tc != nil {
|
||||||
|
s.ocspPeerVerify = true
|
||||||
|
config.apply(tc)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -692,6 +748,11 @@ func (s *Server) reloadOCSP() error {
|
|||||||
// Dispatch all goroutines once again.
|
// Dispatch all goroutines once again.
|
||||||
s.startOCSPMonitoring()
|
s.startOCSPMonitoring()
|
||||||
|
|
||||||
|
// Init and restart OCSP responder cache
|
||||||
|
s.stopOCSPResponseCache()
|
||||||
|
s.initOCSPResponseCache()
|
||||||
|
s.startOCSPResponseCache()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -782,37 +843,81 @@ func parseCertPEM(name string) ([]*x509.Certificate, error) {
|
|||||||
return x509.ParseCertificates(pemBytes)
|
return x509.ParseCertificates(pemBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getOCSPIssuer returns a CA cert from the given path. If the path is empty,
|
// getOCSPIssuerLocally determines a leaf's issuer from locally configured certificates
|
||||||
// then this checks a given cert chain. If both are empty, then it returns an
|
func getOCSPIssuerLocally(trustedCAs []*x509.Certificate, certBundle []*x509.Certificate) (*x509.Certificate, error) {
|
||||||
// error.
|
var vOpts x509.VerifyOptions
|
||||||
func getOCSPIssuer(issuerCert string, chain [][]byte) ([]*x509.Certificate, error) {
|
var leaf *x509.Certificate
|
||||||
var issuers []*x509.Certificate
|
trustedCAPool := x509.NewCertPool()
|
||||||
var err error
|
|
||||||
switch {
|
// Require Leaf as first cert in bundle
|
||||||
case len(chain) == 1 && issuerCert == _EMPTY_:
|
if len(certBundle) > 0 {
|
||||||
err = fmt.Errorf("ocsp ca required in chain or configuration")
|
leaf = certBundle[0]
|
||||||
case issuerCert != _EMPTY_:
|
} else {
|
||||||
issuers, err = parseCertPEM(issuerCert)
|
return nil, fmt.Errorf("invalid ocsp ca configuration")
|
||||||
case len(chain) > 1 && issuerCert == _EMPTY_:
|
|
||||||
issuers, err = x509.ParseCertificates(chain[1])
|
|
||||||
default:
|
|
||||||
err = fmt.Errorf("invalid ocsp ca configuration")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(issuers) == 0 {
|
// Allow Issuer to be configured as second cert in bundle
|
||||||
return nil, fmt.Errorf("no issuers found")
|
if len(certBundle) > 1 {
|
||||||
}
|
// The operator may have misconfigured the cert bundle
|
||||||
|
issuerCandidate := certBundle[1]
|
||||||
for _, issuer := range issuers {
|
err := issuerCandidate.CheckSignature(leaf.SignatureAlgorithm, leaf.RawTBSCertificate, leaf.Signature)
|
||||||
if !issuer.IsCA {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s invalid ca basic constraints: is not ca", issuer.Subject)
|
return nil, fmt.Errorf("invalid issuer configuration: %w", err)
|
||||||
|
} else {
|
||||||
|
return issuerCandidate, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return issuers, nil
|
// Operator did not provide the Leaf Issuer in cert bundle second position
|
||||||
|
// so we will attempt to create at least one ordered verified chain from the
|
||||||
|
// trusted CA pool.
|
||||||
|
|
||||||
|
// Specify CA trust store to validator; if unset, system trust store used
|
||||||
|
if len(trustedCAs) > 0 {
|
||||||
|
for _, ca := range trustedCAs {
|
||||||
|
trustedCAPool.AddCert(ca)
|
||||||
|
}
|
||||||
|
vOpts.Roots = trustedCAPool
|
||||||
|
}
|
||||||
|
|
||||||
|
return certstore.GetLeafIssuer(leaf, vOpts), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getOCSPIssuer determines an issuer certificate from the cert (bundle) or the file-based CA trust store
|
||||||
|
func getOCSPIssuer(caFile string, chain [][]byte) (*x509.Certificate, error) {
|
||||||
|
var issuer *x509.Certificate
|
||||||
|
var trustedCAs []*x509.Certificate
|
||||||
|
var certBundle []*x509.Certificate
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// FIXME(tgb): extend if pluggable CA store provider added to NATS (i.e. other than PEM file)
|
||||||
|
|
||||||
|
// Non-system default CA trust store passed
|
||||||
|
if caFile != _EMPTY_ {
|
||||||
|
trustedCAs, err = parseCertPEM(caFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse ca_file: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify bundled intermediate CA store
|
||||||
|
for _, certBytes := range chain {
|
||||||
|
cert, err := x509.ParseCertificate(certBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse cert: %v", err)
|
||||||
|
}
|
||||||
|
certBundle = append(certBundle, cert)
|
||||||
|
}
|
||||||
|
|
||||||
|
issuer, err = getOCSPIssuerLocally(trustedCAs, certBundle)
|
||||||
|
if err != nil || issuer == nil {
|
||||||
|
return nil, fmt.Errorf("no issuers found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !issuer.IsCA {
|
||||||
|
return nil, fmt.Errorf("%s invalid ca basic constraints: is not ca", issuer.Subject)
|
||||||
|
}
|
||||||
|
return issuer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ocspStatusString(n int) string {
|
func ocspStatusString(n int) string {
|
||||||
|
|||||||
+405
@@ -0,0 +1,405 @@
|
|||||||
|
// Copyright 2023 The NATS Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ocsp"
|
||||||
|
|
||||||
|
"github.com/nats-io/nats-server/v2/server/certidp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseOCSPPeer(v interface{}) (pcfg *certidp.OCSPPeerConfig, retError error) {
|
||||||
|
var lt token
|
||||||
|
defer convertPanicToError(<, &retError)
|
||||||
|
tk, v := unwrapValue(v, <)
|
||||||
|
cm, ok := v.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil, &configErr{tk, fmt.Sprintf(certidp.ErrIllegalPeerOptsConfig, v)}
|
||||||
|
}
|
||||||
|
pcfg = certidp.NewOCSPPeerConfig()
|
||||||
|
retError = nil
|
||||||
|
for mk, mv := range cm {
|
||||||
|
tk, mv = unwrapValue(mv, <)
|
||||||
|
switch strings.ToLower(mk) {
|
||||||
|
case "verify":
|
||||||
|
verify, ok := mv.(bool)
|
||||||
|
if !ok {
|
||||||
|
return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingPeerOptFieldGeneric, mk)}
|
||||||
|
}
|
||||||
|
pcfg.Verify = verify
|
||||||
|
case "allowed_clockskew":
|
||||||
|
at := float64(0)
|
||||||
|
switch mv := mv.(type) {
|
||||||
|
case int64:
|
||||||
|
at = float64(mv)
|
||||||
|
case float64:
|
||||||
|
at = mv
|
||||||
|
case string:
|
||||||
|
d, err := time.ParseDuration(mv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingPeerOptFieldTypeConversion, "unexpected type")}
|
||||||
|
}
|
||||||
|
at = d.Seconds()
|
||||||
|
default:
|
||||||
|
return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingPeerOptFieldTypeConversion, "unexpected type")}
|
||||||
|
}
|
||||||
|
if at >= 0 {
|
||||||
|
pcfg.ClockSkew = at
|
||||||
|
}
|
||||||
|
case "ca_timeout":
|
||||||
|
at := float64(0)
|
||||||
|
switch mv := mv.(type) {
|
||||||
|
case int64:
|
||||||
|
at = float64(mv)
|
||||||
|
case float64:
|
||||||
|
at = mv
|
||||||
|
case string:
|
||||||
|
d, err := time.ParseDuration(mv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingPeerOptFieldTypeConversion, err)}
|
||||||
|
}
|
||||||
|
at = d.Seconds()
|
||||||
|
default:
|
||||||
|
return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingPeerOptFieldTypeConversion, "unexpected type")}
|
||||||
|
}
|
||||||
|
if at >= 0 {
|
||||||
|
pcfg.Timeout = at
|
||||||
|
}
|
||||||
|
case "cache_ttl_when_next_update_unset":
|
||||||
|
at := float64(0)
|
||||||
|
switch mv := mv.(type) {
|
||||||
|
case int64:
|
||||||
|
at = float64(mv)
|
||||||
|
case float64:
|
||||||
|
at = mv
|
||||||
|
case string:
|
||||||
|
d, err := time.ParseDuration(mv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingPeerOptFieldTypeConversion, err)}
|
||||||
|
}
|
||||||
|
at = d.Seconds()
|
||||||
|
default:
|
||||||
|
return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingPeerOptFieldTypeConversion, "unexpected type")}
|
||||||
|
}
|
||||||
|
if at >= 0 {
|
||||||
|
pcfg.TTLUnsetNextUpdate = at
|
||||||
|
}
|
||||||
|
case "warn_only":
|
||||||
|
warnOnly, ok := mv.(bool)
|
||||||
|
if !ok {
|
||||||
|
return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingPeerOptFieldGeneric, mk)}
|
||||||
|
}
|
||||||
|
pcfg.WarnOnly = warnOnly
|
||||||
|
case "unknown_is_good":
|
||||||
|
unknownIsGood, ok := mv.(bool)
|
||||||
|
if !ok {
|
||||||
|
return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingPeerOptFieldGeneric, mk)}
|
||||||
|
}
|
||||||
|
pcfg.UnknownIsGood = unknownIsGood
|
||||||
|
case "allow_when_ca_unreachable":
|
||||||
|
allowWhenCAUnreachable, ok := mv.(bool)
|
||||||
|
if !ok {
|
||||||
|
return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingPeerOptFieldGeneric, mk)}
|
||||||
|
}
|
||||||
|
pcfg.AllowWhenCAUnreachable = allowWhenCAUnreachable
|
||||||
|
default:
|
||||||
|
return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingPeerOptFieldGeneric, mk)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pcfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func peerFromVerifiedChains(chains [][]*x509.Certificate) *x509.Certificate {
|
||||||
|
if len(chains) == 0 || len(chains[0]) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return chains[0][0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// plugTLSOCSPPeer will plug the TLS handshake lifecycle for client mTLS connections and Leaf connections
|
||||||
|
func (s *Server) plugTLSOCSPPeer(config *tlsConfigKind) (*tls.Config, bool, error) {
|
||||||
|
if config == nil || config.tlsConfig == nil {
|
||||||
|
return nil, false, errors.New(certidp.ErrUnableToPlugTLSEmptyConfig)
|
||||||
|
}
|
||||||
|
s.Debugf(certidp.DbgPlugTLSForKind, config.kind)
|
||||||
|
kind := config.kind
|
||||||
|
isSpoke := config.isLeafSpoke
|
||||||
|
tcOpts := config.tlsOpts
|
||||||
|
if tcOpts == nil || tcOpts.OCSPPeerConfig == nil || !tcOpts.OCSPPeerConfig.Verify {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
// peer is a tls client
|
||||||
|
if kind == kindStringMap[CLIENT] || (kind == kindStringMap[LEAF] && !isSpoke) {
|
||||||
|
if !tcOpts.Verify {
|
||||||
|
return nil, false, errors.New(certidp.ErrMTLSRequired)
|
||||||
|
}
|
||||||
|
return s.plugClientTLSOCSPPeer(config)
|
||||||
|
}
|
||||||
|
// peer is a tls server
|
||||||
|
if kind == kindStringMap[LEAF] && isSpoke {
|
||||||
|
return s.plugServerTLSOCSPPeer(config)
|
||||||
|
}
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) plugClientTLSOCSPPeer(config *tlsConfigKind) (*tls.Config, bool, error) {
|
||||||
|
if config == nil || config.tlsConfig == nil || config.tlsOpts == nil {
|
||||||
|
return nil, false, errors.New(certidp.ErrUnableToPlugTLSClient)
|
||||||
|
}
|
||||||
|
tc := config.tlsConfig
|
||||||
|
tcOpts := config.tlsOpts
|
||||||
|
kind := config.kind
|
||||||
|
if tcOpts.OCSPPeerConfig == nil || !tcOpts.OCSPPeerConfig.Verify {
|
||||||
|
return tc, false, nil
|
||||||
|
}
|
||||||
|
tc.VerifyConnection = func(cs tls.ConnectionState) error {
|
||||||
|
if !s.tlsClientOCSPValid(cs.VerifiedChains, tcOpts.OCSPPeerConfig) {
|
||||||
|
s.sendOCSPPeerRejectEvent(kind, peerFromVerifiedChains(cs.VerifiedChains), certidp.MsgTLSClientRejectConnection)
|
||||||
|
return errors.New(certidp.MsgTLSClientRejectConnection)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return tc, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) plugServerTLSOCSPPeer(config *tlsConfigKind) (*tls.Config, bool, error) {
|
||||||
|
if config == nil || config.tlsConfig == nil || config.tlsOpts == nil {
|
||||||
|
return nil, false, errors.New(certidp.ErrUnableToPlugTLSServer)
|
||||||
|
}
|
||||||
|
tc := config.tlsConfig
|
||||||
|
tcOpts := config.tlsOpts
|
||||||
|
kind := config.kind
|
||||||
|
if tcOpts.OCSPPeerConfig == nil || !tcOpts.OCSPPeerConfig.Verify {
|
||||||
|
return tc, false, nil
|
||||||
|
}
|
||||||
|
tc.VerifyConnection = func(cs tls.ConnectionState) error {
|
||||||
|
if !s.tlsServerOCSPValid(cs.VerifiedChains, tcOpts.OCSPPeerConfig) {
|
||||||
|
s.sendOCSPPeerRejectEvent(kind, peerFromVerifiedChains(cs.VerifiedChains), certidp.MsgTLSServerRejectConnection)
|
||||||
|
return errors.New(certidp.MsgTLSServerRejectConnection)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return tc, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// tlsServerOCSPValid evaluates verified chains (post successful TLS handshake) against OCSP
|
||||||
|
// eligibility. A verified chain is considered OCSP Valid if either none of the links are
|
||||||
|
// OCSP eligible, or current "good" responses from the CA can be obtained for each eligible link.
|
||||||
|
// Upon first OCSP Valid chain found, the Server is deemed OCSP Valid. If none of the chains are
|
||||||
|
// OCSP Valid, the Server is deemed OCSP Invalid. A verified self-signed certificate (chain length 1)
|
||||||
|
// is also considered OCSP Valid.
|
||||||
|
func (s *Server) tlsServerOCSPValid(chains [][]*x509.Certificate, opts *certidp.OCSPPeerConfig) bool {
|
||||||
|
s.Debugf(certidp.DbgNumServerChains, len(chains))
|
||||||
|
return s.peerOCSPValid(chains, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// tlsClientOCSPValid evaluates verified chains (post successful TLS handshake) against OCSP
|
||||||
|
// eligibility. A verified chain is considered OCSP Valid if either none of the links are
|
||||||
|
// OCSP eligible, or current "good" responses from the CA can be obtained for each eligible link.
|
||||||
|
// Upon first OCSP Valid chain found, the Client is deemed OCSP Valid. If none of the chains are
|
||||||
|
// OCSP Valid, the Client is deemed OCSP Invalid. A verified self-signed certificate (chain length 1)
|
||||||
|
// is also considered OCSP Valid.
|
||||||
|
func (s *Server) tlsClientOCSPValid(chains [][]*x509.Certificate, opts *certidp.OCSPPeerConfig) bool {
|
||||||
|
s.Debugf(certidp.DbgNumClientChains, len(chains))
|
||||||
|
return s.peerOCSPValid(chains, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) peerOCSPValid(chains [][]*x509.Certificate, opts *certidp.OCSPPeerConfig) bool {
|
||||||
|
peer := peerFromVerifiedChains(chains)
|
||||||
|
if peer == nil {
|
||||||
|
s.Errorf(certidp.ErrPeerEmptyAutoReject)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for ci, chain := range chains {
|
||||||
|
s.Debugf(certidp.DbgLinksInChain, ci, len(chain))
|
||||||
|
// Self-signed certificate is Client OCSP Valid (no CA)
|
||||||
|
if len(chain) == 1 {
|
||||||
|
s.Debugf(certidp.DbgSelfSignedValid, ci)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Check if any of the links in the chain are OCSP eligible
|
||||||
|
chainEligible := false
|
||||||
|
var eligibleLinks []*certidp.ChainLink
|
||||||
|
// Iterate over links skipping the root cert which is not OCSP eligible (self == issuer)
|
||||||
|
for linkPos := 0; linkPos < len(chain)-1; linkPos++ {
|
||||||
|
cert := chain[linkPos]
|
||||||
|
link := &certidp.ChainLink{
|
||||||
|
Leaf: cert,
|
||||||
|
}
|
||||||
|
if certidp.CertOCSPEligible(link) {
|
||||||
|
chainEligible = true
|
||||||
|
issuerCert := certidp.GetLeafIssuerCert(chain, linkPos)
|
||||||
|
if issuerCert == nil {
|
||||||
|
// unexpected chain condition, reject Client as OCSP Invalid
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
link.Issuer = issuerCert
|
||||||
|
eligibleLinks = append(eligibleLinks, link)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// A trust-store verified chain that is not OCSP eligible is always OCSP Valid
|
||||||
|
if !chainEligible {
|
||||||
|
s.Debugf(certidp.DbgValidNonOCSPChain, ci)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
s.Debugf(certidp.DbgChainIsOCSPEligible, ci, len(eligibleLinks))
|
||||||
|
// Chain has at least one OCSP eligible link, so check each eligible link;
|
||||||
|
// any link with a !good OCSP response chain OCSP Invalid
|
||||||
|
chainValid := true
|
||||||
|
for _, link := range eligibleLinks {
|
||||||
|
// if option selected, good could reflect either ocsp.Good or ocsp.Unknown
|
||||||
|
if badReason, good := s.certOCSPGood(link, opts); !good {
|
||||||
|
s.Debugf(badReason)
|
||||||
|
s.sendOCSPPeerChainlinkInvalidEvent(peer, link.Leaf, badReason)
|
||||||
|
chainValid = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if chainValid {
|
||||||
|
s.Debugf(certidp.DbgChainIsOCSPValid, ci)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If we are here, all chains had OCSP eligible links, but none of the chains achieved OCSP valid
|
||||||
|
s.Debugf(certidp.DbgNoOCSPValidChains)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) certOCSPGood(link *certidp.ChainLink, opts *certidp.OCSPPeerConfig) (string, bool) {
|
||||||
|
if link == nil || link.Leaf == nil || link.Issuer == nil || link.OCSPWebEndpoints == nil || len(*link.OCSPWebEndpoints) < 1 {
|
||||||
|
return "Empty chainlink found", false
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
sLogs := &certidp.Log{
|
||||||
|
Debugf: s.Debugf,
|
||||||
|
Noticef: s.Noticef,
|
||||||
|
Warnf: s.Warnf,
|
||||||
|
Errorf: s.Errorf,
|
||||||
|
Tracef: s.Tracef,
|
||||||
|
}
|
||||||
|
fingerprint := certidp.GenerateFingerprint(link.Leaf)
|
||||||
|
// Used for debug/operator only, not match
|
||||||
|
subj := certidp.GetSubjectDNForm(link.Leaf)
|
||||||
|
var rawResp []byte
|
||||||
|
var ocspr *ocsp.Response
|
||||||
|
var useCachedResp bool
|
||||||
|
var rc = s.ocsprc
|
||||||
|
var cachedRevocation bool
|
||||||
|
// Check our cache before calling out to the CA OCSP responder
|
||||||
|
s.Debugf(certidp.DbgCheckingCacheForCert, subj, fingerprint)
|
||||||
|
if rawResp = rc.Get(fingerprint, sLogs); len(rawResp) > 0 {
|
||||||
|
// Signature validation of CA's OCSP response occurs in ParseResponse
|
||||||
|
ocspr, err = ocsp.ParseResponse(rawResp, link.Issuer)
|
||||||
|
if err == nil && ocspr != nil {
|
||||||
|
// Check if OCSP Response delegation present and if so is valid
|
||||||
|
if !certidp.ValidDelegationCheck(link.Issuer, ocspr) {
|
||||||
|
// Invalid delegation was already in cache, purge it and don't use it
|
||||||
|
s.Debugf(certidp.MsgCachedOCSPResponseInvalid, subj)
|
||||||
|
rc.Delete(fingerprint, true, sLogs)
|
||||||
|
goto AFTERCACHE
|
||||||
|
}
|
||||||
|
if certidp.OCSPResponseCurrent(ocspr, opts, sLogs) {
|
||||||
|
s.Debugf(certidp.DbgCurrentResponseCached, certidp.GetStatusAssertionStr(ocspr.Status))
|
||||||
|
useCachedResp = true
|
||||||
|
} else {
|
||||||
|
// Cached response is not current, delete it and tidy runtime stats to reflect a miss;
|
||||||
|
// if preserve_revoked is enabled, the cache will not delete the cached response
|
||||||
|
s.Debugf(certidp.DbgExpiredResponseCached, certidp.GetStatusAssertionStr(ocspr.Status))
|
||||||
|
rc.Delete(fingerprint, true, sLogs)
|
||||||
|
}
|
||||||
|
// Regardless of currency, record a cached revocation found in case AllowWhenCAUnreachable is set
|
||||||
|
if ocspr.Status == ocsp.Revoked {
|
||||||
|
cachedRevocation = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Bogus cached assertion, purge it and don't use it
|
||||||
|
s.Debugf(certidp.MsgCachedOCSPResponseInvalid, subj, fingerprint)
|
||||||
|
rc.Delete(fingerprint, true, sLogs)
|
||||||
|
goto AFTERCACHE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AFTERCACHE:
|
||||||
|
if !useCachedResp {
|
||||||
|
// CA OCSP responder callout needed
|
||||||
|
rawResp, err = certidp.FetchOCSPResponse(link, opts, sLogs)
|
||||||
|
if err != nil || rawResp == nil || len(rawResp) == 0 {
|
||||||
|
s.Warnf(certidp.ErrCAResponderCalloutFail, subj, err)
|
||||||
|
if opts.WarnOnly {
|
||||||
|
s.Warnf(certidp.MsgAllowWarnOnlyOccurred, subj)
|
||||||
|
return _EMPTY_, true
|
||||||
|
}
|
||||||
|
if opts.AllowWhenCAUnreachable && !cachedRevocation {
|
||||||
|
// Link has no cached history of revocation, so allow it to pass
|
||||||
|
s.Warnf(certidp.MsgAllowWhenCAUnreachableOccurred, subj)
|
||||||
|
return _EMPTY_, true
|
||||||
|
} else if opts.AllowWhenCAUnreachable {
|
||||||
|
// Link has cached but expired revocation so reject when CA is unreachable
|
||||||
|
s.Warnf(certidp.MsgAllowWhenCAUnreachableOccurredCachedRevoke, subj)
|
||||||
|
}
|
||||||
|
return certidp.MsgFailedOCSPResponseFetch, false
|
||||||
|
}
|
||||||
|
// Signature validation of CA's OCSP response occurs in ParseResponse
|
||||||
|
ocspr, err = ocsp.ParseResponse(rawResp, link.Issuer)
|
||||||
|
if err == nil && ocspr != nil {
|
||||||
|
// Check if OCSP Response delegation present and if so is valid
|
||||||
|
if !certidp.ValidDelegationCheck(link.Issuer, ocspr) {
|
||||||
|
s.Warnf(certidp.MsgOCSPResponseDelegationInvalid, subj)
|
||||||
|
if opts.WarnOnly {
|
||||||
|
// Can't use bogus assertion, but warn-only set so allow link to pass
|
||||||
|
s.Warnf(certidp.MsgAllowWarnOnlyOccurred, subj)
|
||||||
|
return _EMPTY_, true
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(certidp.MsgOCSPResponseDelegationInvalid, subj), false
|
||||||
|
}
|
||||||
|
if !certidp.OCSPResponseCurrent(ocspr, opts, sLogs) {
|
||||||
|
s.Warnf(certidp.ErrNewCAResponseNotCurrent, subj)
|
||||||
|
if opts.WarnOnly {
|
||||||
|
// Can't use non-effective assertion, but warn-only set so allow link to pass
|
||||||
|
s.Warnf(certidp.MsgAllowWarnOnlyOccurred, subj)
|
||||||
|
return _EMPTY_, true
|
||||||
|
}
|
||||||
|
return certidp.MsgOCSPResponseNotEffective, false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s.Errorf(certidp.ErrCAResponseParseFailed, subj, err)
|
||||||
|
if opts.WarnOnly {
|
||||||
|
// Can't use bogus assertion, but warn-only set so allow link to pass
|
||||||
|
s.Warnf(certidp.MsgAllowWarnOnlyOccurred, subj)
|
||||||
|
return _EMPTY_, true
|
||||||
|
}
|
||||||
|
return certidp.MsgFailedOCSPResponseParse, false
|
||||||
|
}
|
||||||
|
// cache the valid fetched CA OCSP Response
|
||||||
|
rc.Put(fingerprint, ocspr, subj, sLogs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whether through valid cache response available or newly fetched valid response, now check the status
|
||||||
|
if ocspr.Status == ocsp.Revoked || (ocspr.Status == ocsp.Unknown && !opts.UnknownIsGood) {
|
||||||
|
s.Warnf(certidp.ErrOCSPInvalidPeerLink, subj, certidp.GetStatusAssertionStr(ocspr.Status))
|
||||||
|
if opts.WarnOnly {
|
||||||
|
s.Warnf(certidp.MsgAllowWarnOnlyOccurred, subj)
|
||||||
|
return _EMPTY_, true
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(certidp.MsgOCSPResponseInvalidStatus, certidp.GetStatusAssertionStr(ocspr.Status)), false
|
||||||
|
}
|
||||||
|
s.Debugf(certidp.DbgOCSPValidPeerLink, subj)
|
||||||
|
return _EMPTY_, true
|
||||||
|
}
|
||||||
+636
@@ -0,0 +1,636 @@
|
|||||||
|
// Copyright 2023 The NATS Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/klauspost/compress/s2"
|
||||||
|
"golang.org/x/crypto/ocsp"
|
||||||
|
|
||||||
|
"github.com/nats-io/nats-server/v2/server/certidp"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
OCSPResponseCacheDefaultDir = "_rc_"
|
||||||
|
OCSPResponseCacheDefaultFilename = "cache.json"
|
||||||
|
OCSPResponseCacheDefaultTempFilePrefix = "ocsprc-*"
|
||||||
|
OCSPResponseCacheMinimumSaveInterval = 1 * time.Second
|
||||||
|
OCSPResponseCacheDefaultSaveInterval = 5 * time.Minute
|
||||||
|
)
|
||||||
|
|
||||||
|
type OCSPResponseCacheType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
NONE OCSPResponseCacheType = iota + 1
|
||||||
|
LOCAL
|
||||||
|
)
|
||||||
|
|
||||||
|
var OCSPResponseCacheTypeMap = map[string]OCSPResponseCacheType{
|
||||||
|
"none": NONE,
|
||||||
|
"local": LOCAL,
|
||||||
|
}
|
||||||
|
|
||||||
|
type OCSPResponseCacheConfig struct {
|
||||||
|
Type OCSPResponseCacheType
|
||||||
|
LocalStore string
|
||||||
|
PreserveRevoked bool
|
||||||
|
SaveInterval float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOCSPResponseCacheConfig() *OCSPResponseCacheConfig {
|
||||||
|
return &OCSPResponseCacheConfig{
|
||||||
|
Type: LOCAL,
|
||||||
|
LocalStore: OCSPResponseCacheDefaultDir,
|
||||||
|
PreserveRevoked: false,
|
||||||
|
SaveInterval: OCSPResponseCacheDefaultSaveInterval.Seconds(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type OCSPResponseCacheStats struct {
|
||||||
|
Responses int64 `json:"size"`
|
||||||
|
Hits int64 `json:"hits"`
|
||||||
|
Misses int64 `json:"misses"`
|
||||||
|
Revokes int64 `json:"revokes"`
|
||||||
|
Goods int64 `json:"goods"`
|
||||||
|
Unknowns int64 `json:"unknowns"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OCSPResponseCacheItem struct {
|
||||||
|
Subject string `json:"subject,omitempty"`
|
||||||
|
CachedAt time.Time `json:"cached_at"`
|
||||||
|
RespStatus certidp.StatusAssertion `json:"resp_status"`
|
||||||
|
RespExpires time.Time `json:"resp_expires,omitempty"`
|
||||||
|
Resp []byte `json:"resp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OCSPResponseCache interface {
|
||||||
|
Put(key string, resp *ocsp.Response, subj string, log *certidp.Log)
|
||||||
|
Get(key string, log *certidp.Log) []byte
|
||||||
|
Delete(key string, miss bool, log *certidp.Log)
|
||||||
|
Type() string
|
||||||
|
Start(s *Server)
|
||||||
|
Stop(s *Server)
|
||||||
|
Online() bool
|
||||||
|
Config() *OCSPResponseCacheConfig
|
||||||
|
Stats() *OCSPResponseCacheStats
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoOpCache is a no-op implementation of OCSPResponseCache
|
||||||
|
type NoOpCache struct {
|
||||||
|
config *OCSPResponseCacheConfig
|
||||||
|
stats *OCSPResponseCacheStats
|
||||||
|
online bool
|
||||||
|
mu *sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *NoOpCache) Put(_ string, _ *ocsp.Response, _ string, _ *certidp.Log) {}
|
||||||
|
|
||||||
|
func (c *NoOpCache) Get(_ string, _ *certidp.Log) []byte {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *NoOpCache) Delete(_ string, _ bool, _ *certidp.Log) {}
|
||||||
|
|
||||||
|
func (c *NoOpCache) Start(_ *Server) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
c.stats = &OCSPResponseCacheStats{}
|
||||||
|
c.online = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *NoOpCache) Stop(_ *Server) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
c.online = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *NoOpCache) Online() bool {
|
||||||
|
c.mu.RLock()
|
||||||
|
defer c.mu.RUnlock()
|
||||||
|
return c.online
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *NoOpCache) Type() string {
|
||||||
|
c.mu.RLock()
|
||||||
|
defer c.mu.RUnlock()
|
||||||
|
return "none"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *NoOpCache) Config() *OCSPResponseCacheConfig {
|
||||||
|
c.mu.RLock()
|
||||||
|
defer c.mu.RUnlock()
|
||||||
|
return c.config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *NoOpCache) Stats() *OCSPResponseCacheStats {
|
||||||
|
c.mu.RLock()
|
||||||
|
defer c.mu.RUnlock()
|
||||||
|
return c.stats
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalCache is a local file implementation of OCSPResponseCache
|
||||||
|
type LocalCache struct {
|
||||||
|
config *OCSPResponseCacheConfig
|
||||||
|
stats *OCSPResponseCacheStats
|
||||||
|
online bool
|
||||||
|
cache map[string]OCSPResponseCacheItem
|
||||||
|
mu *sync.RWMutex
|
||||||
|
saveInterval time.Duration
|
||||||
|
dirty bool
|
||||||
|
timer *time.Timer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put captures a CA OCSP response to the OCSP peer cache indexed by response fingerprint (a hash)
|
||||||
|
func (c *LocalCache) Put(key string, caResp *ocsp.Response, subj string, log *certidp.Log) {
|
||||||
|
c.mu.RLock()
|
||||||
|
if !c.online || caResp == nil || key == "" {
|
||||||
|
c.mu.RUnlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.mu.RUnlock()
|
||||||
|
log.Debugf(certidp.DbgCachingResponse, subj, key)
|
||||||
|
rawC, err := c.Compress(caResp.Raw)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf(certidp.ErrResponseCompressFail, key, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debugf(certidp.DbgAchievedCompression, float64(len(rawC))/float64(len(caResp.Raw)))
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
// check if we are replacing and do stats
|
||||||
|
item, ok := c.cache[key]
|
||||||
|
if ok {
|
||||||
|
c.adjustStats(-1, item.RespStatus)
|
||||||
|
}
|
||||||
|
item = OCSPResponseCacheItem{
|
||||||
|
Subject: subj,
|
||||||
|
CachedAt: time.Now().UTC().Round(time.Second),
|
||||||
|
RespStatus: certidp.StatusAssertionIntToVal[caResp.Status],
|
||||||
|
RespExpires: caResp.NextUpdate,
|
||||||
|
Resp: rawC,
|
||||||
|
}
|
||||||
|
c.cache[key] = item
|
||||||
|
c.adjustStats(1, item.RespStatus)
|
||||||
|
c.dirty = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns a CA OCSP response from the OCSP peer cache matching the response fingerprint (a hash)
|
||||||
|
func (c *LocalCache) Get(key string, log *certidp.Log) []byte {
|
||||||
|
c.mu.RLock()
|
||||||
|
defer c.mu.RUnlock()
|
||||||
|
if !c.online || key == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
val, ok := c.cache[key]
|
||||||
|
if ok {
|
||||||
|
atomic.AddInt64(&c.stats.Hits, 1)
|
||||||
|
log.Debugf(certidp.DbgCacheHit, key)
|
||||||
|
} else {
|
||||||
|
atomic.AddInt64(&c.stats.Misses, 1)
|
||||||
|
log.Debugf(certidp.DbgCacheMiss, key)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
resp, err := c.Decompress(val.Resp)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf(certidp.ErrResponseDecompressFail, key, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LocalCache) adjustStatsHitToMiss() {
|
||||||
|
atomic.AddInt64(&c.stats.Misses, 1)
|
||||||
|
atomic.AddInt64(&c.stats.Hits, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LocalCache) adjustStats(delta int64, rs certidp.StatusAssertion) {
|
||||||
|
if delta == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
atomic.AddInt64(&c.stats.Responses, delta)
|
||||||
|
switch rs {
|
||||||
|
case ocsp.Good:
|
||||||
|
atomic.AddInt64(&c.stats.Goods, delta)
|
||||||
|
case ocsp.Revoked:
|
||||||
|
atomic.AddInt64(&c.stats.Revokes, delta)
|
||||||
|
case ocsp.Unknown:
|
||||||
|
atomic.AddInt64(&c.stats.Unknowns, delta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes a CA OCSP response from the OCSP peer cache matching the response fingerprint (a hash)
|
||||||
|
func (c *LocalCache) Delete(key string, wasMiss bool, log *certidp.Log) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
if !c.online || key == "" || c.config == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
item, ok := c.cache[key]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if item.RespStatus == ocsp.Revoked && c.config.PreserveRevoked {
|
||||||
|
log.Debugf(certidp.DbgPreservedRevocation, key)
|
||||||
|
if wasMiss {
|
||||||
|
c.adjustStatsHitToMiss()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debugf(certidp.DbgDeletingCacheResponse, key)
|
||||||
|
delete(c.cache, key)
|
||||||
|
c.adjustStats(-1, item.RespStatus)
|
||||||
|
if wasMiss {
|
||||||
|
c.adjustStatsHitToMiss()
|
||||||
|
}
|
||||||
|
c.dirty = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start initializes the configured OCSP peer cache, loads a saved cache from disk (if present), and initializes runtime statistics
|
||||||
|
func (c *LocalCache) Start(s *Server) {
|
||||||
|
s.Debugf(certidp.DbgStartingCache)
|
||||||
|
c.loadCache(s)
|
||||||
|
c.initStats()
|
||||||
|
c.mu.Lock()
|
||||||
|
c.online = true
|
||||||
|
c.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LocalCache) Stop(s *Server) {
|
||||||
|
c.mu.Lock()
|
||||||
|
s.Debugf(certidp.DbgStoppingCache)
|
||||||
|
c.online = false
|
||||||
|
c.timer.Stop()
|
||||||
|
c.mu.Unlock()
|
||||||
|
c.saveCache(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LocalCache) Online() bool {
|
||||||
|
c.mu.RLock()
|
||||||
|
defer c.mu.RUnlock()
|
||||||
|
return c.online
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LocalCache) Type() string {
|
||||||
|
c.mu.RLock()
|
||||||
|
defer c.mu.RUnlock()
|
||||||
|
return "local"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LocalCache) Config() *OCSPResponseCacheConfig {
|
||||||
|
c.mu.RLock()
|
||||||
|
defer c.mu.RUnlock()
|
||||||
|
return c.config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LocalCache) Stats() *OCSPResponseCacheStats {
|
||||||
|
c.mu.RLock()
|
||||||
|
defer c.mu.RUnlock()
|
||||||
|
if c.stats == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
stats := OCSPResponseCacheStats{
|
||||||
|
Responses: c.stats.Responses,
|
||||||
|
Hits: c.stats.Hits,
|
||||||
|
Misses: c.stats.Misses,
|
||||||
|
Revokes: c.stats.Revokes,
|
||||||
|
Goods: c.stats.Goods,
|
||||||
|
Unknowns: c.stats.Unknowns,
|
||||||
|
}
|
||||||
|
return &stats
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LocalCache) initStats() {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
c.stats = &OCSPResponseCacheStats{}
|
||||||
|
c.stats.Hits = 0
|
||||||
|
c.stats.Misses = 0
|
||||||
|
c.stats.Responses = int64(len(c.cache))
|
||||||
|
for _, resp := range c.cache {
|
||||||
|
switch resp.RespStatus {
|
||||||
|
case ocsp.Good:
|
||||||
|
c.stats.Goods++
|
||||||
|
case ocsp.Revoked:
|
||||||
|
c.stats.Revokes++
|
||||||
|
case ocsp.Unknown:
|
||||||
|
c.stats.Unknowns++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LocalCache) Compress(buf []byte) ([]byte, error) {
|
||||||
|
bodyLen := int64(len(buf))
|
||||||
|
var output bytes.Buffer
|
||||||
|
writer := s2.NewWriter(&output)
|
||||||
|
input := bytes.NewReader(buf[:bodyLen])
|
||||||
|
if n, err := io.CopyN(writer, input, bodyLen); err != nil {
|
||||||
|
return nil, fmt.Errorf(certidp.ErrCannotWriteCompressed, err)
|
||||||
|
} else if n != bodyLen {
|
||||||
|
return nil, fmt.Errorf(certidp.ErrTruncatedWrite, n, bodyLen)
|
||||||
|
}
|
||||||
|
if err := writer.Close(); err != nil {
|
||||||
|
return nil, fmt.Errorf(certidp.ErrCannotCloseWriter, err)
|
||||||
|
}
|
||||||
|
return output.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LocalCache) Decompress(buf []byte) ([]byte, error) {
|
||||||
|
bodyLen := int64(len(buf))
|
||||||
|
input := bytes.NewReader(buf[:bodyLen])
|
||||||
|
reader := io.NopCloser(s2.NewReader(input))
|
||||||
|
output, err := io.ReadAll(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(certidp.ErrCannotReadCompressed, err)
|
||||||
|
}
|
||||||
|
return output, reader.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LocalCache) loadCache(s *Server) {
|
||||||
|
d := s.opts.OCSPCacheConfig.LocalStore
|
||||||
|
if d == _EMPTY_ {
|
||||||
|
d = OCSPResponseCacheDefaultDir
|
||||||
|
}
|
||||||
|
f := OCSPResponseCacheDefaultFilename
|
||||||
|
store, err := filepath.Abs(path.Join(d, f))
|
||||||
|
if err != nil {
|
||||||
|
s.Errorf(certidp.ErrLoadCacheFail, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.Debugf(certidp.DbgLoadingCache, store)
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
c.cache = make(map[string]OCSPResponseCacheItem)
|
||||||
|
dat, err := os.ReadFile(store)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
s.Debugf(certidp.DbgNoCacheFound)
|
||||||
|
} else {
|
||||||
|
s.Warnf(certidp.ErrLoadCacheFail, err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(dat, &c.cache)
|
||||||
|
if err != nil {
|
||||||
|
// make sure clean cache
|
||||||
|
c.cache = make(map[string]OCSPResponseCacheItem)
|
||||||
|
s.Warnf(certidp.ErrLoadCacheFail, err)
|
||||||
|
c.dirty = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.dirty = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LocalCache) saveCache(s *Server) {
|
||||||
|
c.mu.RLock()
|
||||||
|
dirty := c.dirty
|
||||||
|
c.mu.RUnlock()
|
||||||
|
if !dirty {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.Debugf(certidp.DbgCacheDirtySave)
|
||||||
|
var d string
|
||||||
|
if c.config.LocalStore != _EMPTY_ {
|
||||||
|
d = c.config.LocalStore
|
||||||
|
} else {
|
||||||
|
d = OCSPResponseCacheDefaultDir
|
||||||
|
}
|
||||||
|
f := OCSPResponseCacheDefaultFilename
|
||||||
|
store, err := filepath.Abs(path.Join(d, f))
|
||||||
|
if err != nil {
|
||||||
|
s.Errorf(certidp.ErrSaveCacheFail, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.Debugf(certidp.DbgSavingCache, store)
|
||||||
|
if _, err := os.Stat(d); os.IsNotExist(err) {
|
||||||
|
err = os.Mkdir(d, defaultDirPerms)
|
||||||
|
if err != nil {
|
||||||
|
s.Errorf(certidp.ErrSaveCacheFail, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tmp, err := os.CreateTemp(d, OCSPResponseCacheDefaultTempFilePrefix)
|
||||||
|
if err != nil {
|
||||||
|
s.Errorf(certidp.ErrSaveCacheFail, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
tmp.Close()
|
||||||
|
os.Remove(tmp.Name())
|
||||||
|
}() // clean up any temp files
|
||||||
|
|
||||||
|
// RW lock here because we're going to snapshot the cache to disk and mark as clean if successful
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
dat, err := json.MarshalIndent(c.cache, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
s.Errorf(certidp.ErrSaveCacheFail, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cacheSize, err := tmp.Write(dat)
|
||||||
|
if err != nil {
|
||||||
|
s.Errorf(certidp.ErrSaveCacheFail, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = tmp.Sync()
|
||||||
|
if err != nil {
|
||||||
|
s.Errorf(certidp.ErrSaveCacheFail, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = tmp.Close()
|
||||||
|
if err != nil {
|
||||||
|
s.Errorf(certidp.ErrSaveCacheFail, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// do the final swap and overwrite any old saved peer cache
|
||||||
|
err = os.Rename(tmp.Name(), store)
|
||||||
|
if err != nil {
|
||||||
|
s.Errorf(certidp.ErrSaveCacheFail, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.dirty = false
|
||||||
|
s.Debugf(certidp.DbgCacheSaved, cacheSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
var OCSPResponseCacheUsage = `
|
||||||
|
You may enable OCSP peer response cacheing at server configuration root level:
|
||||||
|
|
||||||
|
(If no TLS blocks are configured with OCSP peer verification, ocsp_cache is ignored.)
|
||||||
|
|
||||||
|
...
|
||||||
|
# short form enables with defaults
|
||||||
|
ocsp_cache: true
|
||||||
|
|
||||||
|
# if false or undefined and one or more TLS blocks are configured with OCSP peer verification, "none" is implied
|
||||||
|
|
||||||
|
# long form includes settable options
|
||||||
|
ocsp_cache {
|
||||||
|
|
||||||
|
# Cache type <none, local> (default local)
|
||||||
|
type: local
|
||||||
|
|
||||||
|
# Cache file directory for local-type cache (default _rc_ in current working directory)
|
||||||
|
local_store: "_rc_"
|
||||||
|
|
||||||
|
# Ignore cache deletes if cached OCSP response is Revoked status (default false)
|
||||||
|
preserve_revoked: false
|
||||||
|
|
||||||
|
# For local store, interval to save in-memory cache to disk in seconds (default 300 seconds, minimum 1 second)
|
||||||
|
save_interval: 300
|
||||||
|
}
|
||||||
|
...
|
||||||
|
|
||||||
|
Note: Cache of server's own OCSP response (staple) is enabled using the 'ocsp' configuration option.
|
||||||
|
`
|
||||||
|
|
||||||
|
func (s *Server) initOCSPResponseCache() {
|
||||||
|
// No mTLS OCSP or Leaf OCSP enablements, so no need to init cache
|
||||||
|
s.mu.RLock()
|
||||||
|
if !s.ocspPeerVerify {
|
||||||
|
s.mu.RUnlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.mu.RUnlock()
|
||||||
|
so := s.getOpts()
|
||||||
|
if so.OCSPCacheConfig == nil {
|
||||||
|
so.OCSPCacheConfig = NewOCSPResponseCacheConfig()
|
||||||
|
}
|
||||||
|
var cc = so.OCSPCacheConfig
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
switch cc.Type {
|
||||||
|
case NONE:
|
||||||
|
s.ocsprc = &NoOpCache{config: cc, online: true, mu: &sync.RWMutex{}}
|
||||||
|
case LOCAL:
|
||||||
|
c := &LocalCache{
|
||||||
|
config: cc,
|
||||||
|
online: false,
|
||||||
|
cache: make(map[string]OCSPResponseCacheItem),
|
||||||
|
mu: &sync.RWMutex{},
|
||||||
|
dirty: false,
|
||||||
|
}
|
||||||
|
c.saveInterval = time.Duration(cc.SaveInterval) * time.Second
|
||||||
|
c.timer = time.AfterFunc(c.saveInterval, func() {
|
||||||
|
s.Debugf(certidp.DbgCacheSaveTimerExpired)
|
||||||
|
c.saveCache(s)
|
||||||
|
c.timer.Reset(c.saveInterval)
|
||||||
|
})
|
||||||
|
s.ocsprc = c
|
||||||
|
default:
|
||||||
|
s.Fatalf(certidp.ErrBadCacheTypeConfig, cc.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) startOCSPResponseCache() {
|
||||||
|
// No mTLS OCSP or Leaf OCSP enablements, so no need to start cache
|
||||||
|
s.mu.RLock()
|
||||||
|
if !s.ocspPeerVerify || s.ocsprc == nil {
|
||||||
|
s.mu.RUnlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.mu.RUnlock()
|
||||||
|
|
||||||
|
// Could be heavier operation depending on cache implementation
|
||||||
|
s.ocsprc.Start(s)
|
||||||
|
if s.ocsprc.Online() {
|
||||||
|
s.Noticef(certidp.MsgCacheOnline, s.ocsprc.Type())
|
||||||
|
} else {
|
||||||
|
s.Noticef(certidp.MsgCacheOffline, s.ocsprc.Type())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) stopOCSPResponseCache() {
|
||||||
|
s.mu.RLock()
|
||||||
|
if s.ocsprc == nil {
|
||||||
|
s.mu.RUnlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.mu.RUnlock()
|
||||||
|
s.ocsprc.Stop(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseOCSPResponseCache(v interface{}) (pcfg *OCSPResponseCacheConfig, retError error) {
|
||||||
|
var lt token
|
||||||
|
defer convertPanicToError(<, &retError)
|
||||||
|
tk, v := unwrapValue(v, <)
|
||||||
|
cm, ok := v.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil, &configErr{tk, fmt.Sprintf(certidp.ErrIllegalCacheOptsConfig, v)}
|
||||||
|
}
|
||||||
|
pcfg = NewOCSPResponseCacheConfig()
|
||||||
|
retError = nil
|
||||||
|
for mk, mv := range cm {
|
||||||
|
// Again, unwrap token value if line check is required.
|
||||||
|
tk, mv = unwrapValue(mv, <)
|
||||||
|
switch strings.ToLower(mk) {
|
||||||
|
case "type":
|
||||||
|
cache, ok := mv.(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingCacheOptFieldGeneric, mk)}
|
||||||
|
}
|
||||||
|
cacheType, exists := OCSPResponseCacheTypeMap[strings.ToLower(cache)]
|
||||||
|
if !exists {
|
||||||
|
return nil, &configErr{tk, fmt.Sprintf(certidp.ErrUnknownCacheType, cache)}
|
||||||
|
}
|
||||||
|
pcfg.Type = cacheType
|
||||||
|
case "local_store":
|
||||||
|
store, ok := mv.(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingCacheOptFieldGeneric, mk)}
|
||||||
|
}
|
||||||
|
pcfg.LocalStore = store
|
||||||
|
case "preserve_revoked":
|
||||||
|
preserve, ok := mv.(bool)
|
||||||
|
if !ok {
|
||||||
|
return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingCacheOptFieldGeneric, mk)}
|
||||||
|
}
|
||||||
|
pcfg.PreserveRevoked = preserve
|
||||||
|
case "save_interval":
|
||||||
|
at := float64(0)
|
||||||
|
switch mv := mv.(type) {
|
||||||
|
case int64:
|
||||||
|
at = float64(mv)
|
||||||
|
case float64:
|
||||||
|
at = mv
|
||||||
|
case string:
|
||||||
|
d, err := time.ParseDuration(mv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingPeerOptFieldTypeConversion, err)}
|
||||||
|
}
|
||||||
|
at = d.Seconds()
|
||||||
|
default:
|
||||||
|
return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingCacheOptFieldTypeConversion, "unexpected type")}
|
||||||
|
}
|
||||||
|
si := time.Duration(at) * time.Second
|
||||||
|
if si < OCSPResponseCacheMinimumSaveInterval {
|
||||||
|
si = OCSPResponseCacheMinimumSaveInterval
|
||||||
|
}
|
||||||
|
pcfg.SaveInterval = si.Seconds()
|
||||||
|
default:
|
||||||
|
return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingCacheOptFieldGeneric, mk)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pcfg, nil
|
||||||
|
}
|
||||||
+134
-17
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2012-2022 The NATS Authors
|
// Copyright 2012-2023 The NATS Authors
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
@@ -34,9 +34,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nats-io/jwt/v2"
|
"github.com/nats-io/jwt/v2"
|
||||||
"github.com/nats-io/nkeys"
|
|
||||||
|
|
||||||
"github.com/nats-io/nats-server/v2/conf"
|
"github.com/nats-io/nats-server/v2/conf"
|
||||||
|
"github.com/nats-io/nats-server/v2/server/certidp"
|
||||||
|
"github.com/nats-io/nats-server/v2/server/certstore"
|
||||||
|
"github.com/nats-io/nkeys"
|
||||||
)
|
)
|
||||||
|
|
||||||
var allowUnknownTopLevelField = int32(0)
|
var allowUnknownTopLevelField = int32(0)
|
||||||
@@ -53,7 +54,7 @@ func NoErrOnUnknownFields(noError bool) {
|
|||||||
atomic.StoreInt32(&allowUnknownTopLevelField, val)
|
atomic.StoreInt32(&allowUnknownTopLevelField, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set of lower case hex-encoded sha256 of DER encoded SubjectPublicKeyInfo
|
// PinnedCertSet is a set of lower case hex-encoded sha256 of DER encoded SubjectPublicKeyInfo
|
||||||
type PinnedCertSet map[string]struct{}
|
type PinnedCertSet map[string]struct{}
|
||||||
|
|
||||||
// ClusterOpts are options for clusters.
|
// ClusterOpts are options for clusters.
|
||||||
@@ -221,6 +222,7 @@ type Options struct {
|
|||||||
NoHeaderSupport bool `json:"-"`
|
NoHeaderSupport bool `json:"-"`
|
||||||
DisableShortFirstPing bool `json:"-"`
|
DisableShortFirstPing bool `json:"-"`
|
||||||
Logtime bool `json:"-"`
|
Logtime bool `json:"-"`
|
||||||
|
LogtimeUTC bool `json:"-"`
|
||||||
MaxConn int `json:"max_connections"`
|
MaxConn int `json:"max_connections"`
|
||||||
MaxSubs int `json:"max_subscriptions,omitempty"`
|
MaxSubs int `json:"max_subscriptions,omitempty"`
|
||||||
MaxSubTokens uint8 `json:"-"`
|
MaxSubTokens uint8 `json:"-"`
|
||||||
@@ -340,6 +342,9 @@ type Options struct {
|
|||||||
// JetStream
|
// JetStream
|
||||||
maxMemSet bool
|
maxMemSet bool
|
||||||
maxStoreSet bool
|
maxStoreSet bool
|
||||||
|
|
||||||
|
// OCSP Cache config enables next-gen cache for OCSP features
|
||||||
|
OCSPCacheConfig *OCSPResponseCacheConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebsocketOpts are options for websocket
|
// WebsocketOpts are options for websocket
|
||||||
@@ -403,6 +408,9 @@ type WebsocketOpts struct {
|
|||||||
// and write the response back to the client. This include the
|
// and write the response back to the client. This include the
|
||||||
// time needed for the TLS Handshake.
|
// time needed for the TLS Handshake.
|
||||||
HandshakeTimeout time.Duration
|
HandshakeTimeout time.Duration
|
||||||
|
|
||||||
|
// Snapshot of configured TLS options.
|
||||||
|
tlsConfigOpts *TLSConfigOpts
|
||||||
}
|
}
|
||||||
|
|
||||||
// MQTTOpts are options for MQTT
|
// MQTTOpts are options for MQTT
|
||||||
@@ -483,6 +491,9 @@ type MQTTOpts struct {
|
|||||||
// subscription ending with "#" will use 2 times the MaxAckPending value.
|
// subscription ending with "#" will use 2 times the MaxAckPending value.
|
||||||
// Note that changes to this option is applied only to new subscriptions.
|
// Note that changes to this option is applied only to new subscriptions.
|
||||||
MaxAckPending uint16
|
MaxAckPending uint16
|
||||||
|
|
||||||
|
// Snapshot of configured TLS options.
|
||||||
|
tlsConfigOpts *TLSConfigOpts
|
||||||
}
|
}
|
||||||
|
|
||||||
type netResolver interface {
|
type netResolver interface {
|
||||||
@@ -574,6 +585,10 @@ type TLSConfigOpts struct {
|
|||||||
Ciphers []uint16
|
Ciphers []uint16
|
||||||
CurvePreferences []tls.CurveID
|
CurvePreferences []tls.CurveID
|
||||||
PinnedCerts PinnedCertSet
|
PinnedCerts PinnedCertSet
|
||||||
|
CertStore certstore.StoreType
|
||||||
|
CertMatchBy certstore.MatchByType
|
||||||
|
CertMatch string
|
||||||
|
OCSPPeerConfig *certidp.OCSPPeerConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// OCSPConfig represents the options of OCSP stapling options.
|
// OCSPConfig represents the options of OCSP stapling options.
|
||||||
@@ -786,6 +801,9 @@ func (o *Options) processConfigFileLine(k string, v interface{}, errors *[]error
|
|||||||
case "logtime":
|
case "logtime":
|
||||||
o.Logtime = v.(bool)
|
o.Logtime = v.(bool)
|
||||||
trackExplicitVal(o, &o.inConfig, "Logtime", o.Logtime)
|
trackExplicitVal(o, &o.inConfig, "Logtime", o.Logtime)
|
||||||
|
case "logtime_utc":
|
||||||
|
o.LogtimeUTC = v.(bool)
|
||||||
|
trackExplicitVal(o, &o.inConfig, "LogtimeUTC", o.LogtimeUTC)
|
||||||
case "mappings", "maps":
|
case "mappings", "maps":
|
||||||
gacc := NewAccount(globalAccountName)
|
gacc := NewAccount(globalAccountName)
|
||||||
o.Accounts = append(o.Accounts, gacc)
|
o.Accounts = append(o.Accounts, gacc)
|
||||||
@@ -1179,17 +1197,22 @@ func (o *Options) processConfigFileLine(k string, v interface{}, errors *[]error
|
|||||||
*errors = append(*errors, &configErr{tk, err.Error()})
|
*errors = append(*errors, &configErr{tk, err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if dir == "" {
|
|
||||||
*errors = append(*errors, &configErr{tk, "dir has no value and needs to point to a directory"})
|
checkDir := func() {
|
||||||
return
|
if dir == _EMPTY_ {
|
||||||
}
|
*errors = append(*errors, &configErr{tk, "dir has no value and needs to point to a directory"})
|
||||||
if info, _ := os.Stat(dir); info != nil && (!info.IsDir() || info.Mode().Perm()&(1<<(uint(7))) == 0) {
|
return
|
||||||
*errors = append(*errors, &configErr{tk, "dir needs to point to an accessible directory"})
|
}
|
||||||
return
|
if info, _ := os.Stat(dir); info != nil && (!info.IsDir() || info.Mode().Perm()&(1<<(uint(7))) == 0) {
|
||||||
|
*errors = append(*errors, &configErr{tk, "dir needs to point to an accessible directory"})
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var res AccountResolver
|
var res AccountResolver
|
||||||
switch strings.ToUpper(dirType) {
|
switch strings.ToUpper(dirType) {
|
||||||
case "CACHE":
|
case "CACHE":
|
||||||
|
checkDir()
|
||||||
if sync != 0 {
|
if sync != 0 {
|
||||||
*errors = append(*errors, &configErr{tk, "CACHE does not accept sync"})
|
*errors = append(*errors, &configErr{tk, "CACHE does not accept sync"})
|
||||||
}
|
}
|
||||||
@@ -1201,6 +1224,7 @@ func (o *Options) processConfigFileLine(k string, v interface{}, errors *[]error
|
|||||||
}
|
}
|
||||||
res, err = NewCacheDirAccResolver(dir, limit, ttl, opts...)
|
res, err = NewCacheDirAccResolver(dir, limit, ttl, opts...)
|
||||||
case "FULL":
|
case "FULL":
|
||||||
|
checkDir()
|
||||||
if ttl != 0 {
|
if ttl != 0 {
|
||||||
*errors = append(*errors, &configErr{tk, "FULL does not accept ttl"})
|
*errors = append(*errors, &configErr{tk, "FULL does not accept ttl"})
|
||||||
}
|
}
|
||||||
@@ -1216,6 +1240,8 @@ func (o *Options) processConfigFileLine(k string, v interface{}, errors *[]error
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
res, err = NewDirAccResolver(dir, limit, sync, delete, opts...)
|
res, err = NewDirAccResolver(dir, limit, sync, delete, opts...)
|
||||||
|
case "MEM", "MEMORY":
|
||||||
|
res = &MemAccResolver{}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
*errors = append(*errors, &configErr{tk, err.Error()})
|
*errors = append(*errors, &configErr{tk, err.Error()})
|
||||||
@@ -1393,6 +1419,34 @@ func (o *Options) processConfigFileLine(k string, v interface{}, errors *[]error
|
|||||||
m[kk] = v.(string)
|
m[kk] = v.(string)
|
||||||
}
|
}
|
||||||
o.JsAccDefaultDomain = m
|
o.JsAccDefaultDomain = m
|
||||||
|
case "ocsp_cache":
|
||||||
|
var err error
|
||||||
|
switch vv := v.(type) {
|
||||||
|
case bool:
|
||||||
|
pc := NewOCSPResponseCacheConfig()
|
||||||
|
if vv {
|
||||||
|
// Set enabled
|
||||||
|
pc.Type = LOCAL
|
||||||
|
o.OCSPCacheConfig = pc
|
||||||
|
} else {
|
||||||
|
// Set disabled (none cache)
|
||||||
|
pc.Type = NONE
|
||||||
|
o.OCSPCacheConfig = pc
|
||||||
|
}
|
||||||
|
case map[string]interface{}:
|
||||||
|
pc, err := parseOCSPResponseCache(v)
|
||||||
|
if err != nil {
|
||||||
|
*errors = append(*errors, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
o.OCSPCacheConfig = pc
|
||||||
|
default:
|
||||||
|
err = &configErr{tk, fmt.Sprintf("error parsing tags: unsupported type %T", v)}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
*errors = append(*errors, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
if au := atomic.LoadInt32(&allowUnknownTopLevelField); au == 0 && !tk.IsUsedVariable() {
|
if au := atomic.LoadInt32(&allowUnknownTopLevelField); au == 0 && !tk.IsUsedVariable() {
|
||||||
err := &unknownConfigFieldErr{
|
err := &unknownConfigFieldErr{
|
||||||
@@ -3847,6 +3901,11 @@ func PrintTLSHelpAndDie() {
|
|||||||
for k := range curvePreferenceMap {
|
for k := range curvePreferenceMap {
|
||||||
fmt.Printf(" %s\n", k)
|
fmt.Printf(" %s\n", k)
|
||||||
}
|
}
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
fmt.Printf("%s\n", certstore.Usage)
|
||||||
|
}
|
||||||
|
fmt.Printf("%s", certidp.OCSPPeerUsage)
|
||||||
|
fmt.Printf("%s", OCSPResponseCacheUsage)
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4004,6 +4063,54 @@ func parseTLS(v interface{}, isClientCtx bool) (t *TLSConfigOpts, retErr error)
|
|||||||
}
|
}
|
||||||
tc.PinnedCerts = wl
|
tc.PinnedCerts = wl
|
||||||
}
|
}
|
||||||
|
case "cert_store":
|
||||||
|
certStore, ok := mv.(string)
|
||||||
|
if !ok || certStore == _EMPTY_ {
|
||||||
|
return nil, &configErr{tk, certstore.ErrBadCertStoreField.Error()}
|
||||||
|
}
|
||||||
|
certStoreType, err := certstore.ParseCertStore(certStore)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &configErr{tk, err.Error()}
|
||||||
|
}
|
||||||
|
tc.CertStore = certStoreType
|
||||||
|
case "cert_match_by":
|
||||||
|
certMatchBy, ok := mv.(string)
|
||||||
|
if !ok || certMatchBy == _EMPTY_ {
|
||||||
|
return nil, &configErr{tk, certstore.ErrBadCertMatchByField.Error()}
|
||||||
|
}
|
||||||
|
certMatchByType, err := certstore.ParseCertMatchBy(certMatchBy)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &configErr{tk, err.Error()}
|
||||||
|
}
|
||||||
|
tc.CertMatchBy = certMatchByType
|
||||||
|
case "cert_match":
|
||||||
|
certMatch, ok := mv.(string)
|
||||||
|
if !ok || certMatch == _EMPTY_ {
|
||||||
|
return nil, &configErr{tk, certstore.ErrBadCertMatchField.Error()}
|
||||||
|
}
|
||||||
|
tc.CertMatch = certMatch
|
||||||
|
case "ocsp_peer":
|
||||||
|
switch vv := mv.(type) {
|
||||||
|
case bool:
|
||||||
|
pc := certidp.NewOCSPPeerConfig()
|
||||||
|
if vv {
|
||||||
|
// Set enabled
|
||||||
|
pc.Verify = true
|
||||||
|
tc.OCSPPeerConfig = pc
|
||||||
|
} else {
|
||||||
|
// Set disabled
|
||||||
|
pc.Verify = false
|
||||||
|
tc.OCSPPeerConfig = pc
|
||||||
|
}
|
||||||
|
case map[string]interface{}:
|
||||||
|
pc, err := parseOCSPPeer(mv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &configErr{tk, err.Error()}
|
||||||
|
}
|
||||||
|
tc.OCSPPeerConfig = pc
|
||||||
|
default:
|
||||||
|
return nil, &configErr{tk, fmt.Sprintf("error parsing ocsp peer config: unsupported type %T", v)}
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return nil, &configErr{tk, fmt.Sprintf("error parsing tls config, unknown field [%q]", mk)}
|
return nil, &configErr{tk, fmt.Sprintf("error parsing tls config, unknown field [%q]", mk)}
|
||||||
}
|
}
|
||||||
@@ -4134,6 +4241,7 @@ func parseWebsocket(v interface{}, o *Options, errors *[]error, warnings *[]erro
|
|||||||
}
|
}
|
||||||
o.Websocket.TLSMap = tc.Map
|
o.Websocket.TLSMap = tc.Map
|
||||||
o.Websocket.TLSPinnedCerts = tc.PinnedCerts
|
o.Websocket.TLSPinnedCerts = tc.PinnedCerts
|
||||||
|
o.Websocket.tlsConfigOpts = tc
|
||||||
case "same_origin":
|
case "same_origin":
|
||||||
o.Websocket.SameOrigin = mv.(bool)
|
o.Websocket.SameOrigin = mv.(bool)
|
||||||
case "allowed_origins", "allowed_origin", "allow_origins", "allow_origin", "origins", "origin":
|
case "allowed_origins", "allowed_origin", "allow_origins", "allow_origin", "origins", "origin":
|
||||||
@@ -4224,6 +4332,7 @@ func parseMQTT(v interface{}, o *Options, errors *[]error, warnings *[]error) er
|
|||||||
o.MQTT.TLSTimeout = tc.Timeout
|
o.MQTT.TLSTimeout = tc.Timeout
|
||||||
o.MQTT.TLSMap = tc.Map
|
o.MQTT.TLSMap = tc.Map
|
||||||
o.MQTT.TLSPinnedCerts = tc.PinnedCerts
|
o.MQTT.TLSPinnedCerts = tc.PinnedCerts
|
||||||
|
o.MQTT.tlsConfigOpts = tc
|
||||||
case "authorization", "authentication":
|
case "authorization", "authentication":
|
||||||
auth := parseSimpleAuth(tk, errors, warnings)
|
auth := parseSimpleAuth(tk, errors, warnings)
|
||||||
o.MQTT.Username = auth.user
|
o.MQTT.Username = auth.user
|
||||||
@@ -4290,11 +4399,13 @@ func GenTLSConfig(tc *TLSConfigOpts) (*tls.Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case tc.CertFile != "" && tc.KeyFile == "":
|
case tc.CertFile != _EMPTY_ && tc.CertStore != certstore.STOREEMPTY:
|
||||||
|
return nil, certstore.ErrConflictCertFileAndStore
|
||||||
|
case tc.CertFile != _EMPTY_ && tc.KeyFile == _EMPTY_:
|
||||||
return nil, fmt.Errorf("missing 'key_file' in TLS configuration")
|
return nil, fmt.Errorf("missing 'key_file' in TLS configuration")
|
||||||
case tc.CertFile == "" && tc.KeyFile != "":
|
case tc.CertFile == _EMPTY_ && tc.KeyFile != _EMPTY_:
|
||||||
return nil, fmt.Errorf("missing 'cert_file' in TLS configuration")
|
return nil, fmt.Errorf("missing 'cert_file' in TLS configuration")
|
||||||
case tc.CertFile != "" && tc.KeyFile != "":
|
case tc.CertFile != _EMPTY_ && tc.KeyFile != _EMPTY_:
|
||||||
// Now load in cert and private key
|
// Now load in cert and private key
|
||||||
cert, err := tls.LoadX509KeyPair(tc.CertFile, tc.KeyFile)
|
cert, err := tls.LoadX509KeyPair(tc.CertFile, tc.KeyFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -4305,6 +4416,11 @@ func GenTLSConfig(tc *TLSConfigOpts) (*tls.Config, error) {
|
|||||||
return nil, fmt.Errorf("error parsing certificate: %v", err)
|
return nil, fmt.Errorf("error parsing certificate: %v", err)
|
||||||
}
|
}
|
||||||
config.Certificates = []tls.Certificate{cert}
|
config.Certificates = []tls.Certificate{cert}
|
||||||
|
case tc.CertStore != certstore.STOREEMPTY:
|
||||||
|
err := certstore.TLSConfig(tc.CertStore, tc.CertMatchBy, tc.CertMatch, &config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Require client certificates as needed
|
// Require client certificates as needed
|
||||||
@@ -4690,9 +4806,10 @@ func ConfigureOptions(fs *flag.FlagSet, args []string, printVersion, printHelp,
|
|||||||
fs.BoolVar(&dbgAndTrcAndVerboseTrc, "DVV", false, "Enable Debug and Verbose Trace logging. (Traces system account as well)")
|
fs.BoolVar(&dbgAndTrcAndVerboseTrc, "DVV", false, "Enable Debug and Verbose Trace logging. (Traces system account as well)")
|
||||||
fs.BoolVar(&opts.Logtime, "T", true, "Timestamp log entries.")
|
fs.BoolVar(&opts.Logtime, "T", true, "Timestamp log entries.")
|
||||||
fs.BoolVar(&opts.Logtime, "logtime", true, "Timestamp log entries.")
|
fs.BoolVar(&opts.Logtime, "logtime", true, "Timestamp log entries.")
|
||||||
fs.StringVar(&opts.Username, "user", "", "Username required for connection.")
|
fs.BoolVar(&opts.LogtimeUTC, "logtime_utc", false, "Timestamps in UTC instead of local timezone.")
|
||||||
fs.StringVar(&opts.Password, "pass", "", "Password required for connection.")
|
fs.StringVar(&opts.Username, "user", _EMPTY_, "Username required for connection.")
|
||||||
fs.StringVar(&opts.Authorization, "auth", "", "Authorization token required for connection.")
|
fs.StringVar(&opts.Password, "pass", _EMPTY_, "Password required for connection.")
|
||||||
|
fs.StringVar(&opts.Authorization, "auth", _EMPTY_, "Authorization token required for connection.")
|
||||||
fs.IntVar(&opts.HTTPPort, "m", 0, "HTTP Port for /varz, /connz endpoints.")
|
fs.IntVar(&opts.HTTPPort, "m", 0, "HTTP Port for /varz, /connz endpoints.")
|
||||||
fs.IntVar(&opts.HTTPPort, "http_port", 0, "HTTP Port for /varz, /connz endpoints.")
|
fs.IntVar(&opts.HTTPPort, "http_port", 0, "HTTP Port for /varz, /connz endpoints.")
|
||||||
fs.IntVar(&opts.HTTPSPort, "ms", 0, "HTTPS Port for /varz, /connz endpoints.")
|
fs.IntVar(&opts.HTTPSPort, "ms", 0, "HTTPS Port for /varz, /connz endpoints.")
|
||||||
|
|||||||
+33
-7
@@ -162,6 +162,17 @@ func (l *logtimeOption) Apply(server *Server) {
|
|||||||
server.Noticef("Reloaded: logtime = %v", l.newValue)
|
server.Noticef("Reloaded: logtime = %v", l.newValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// logtimeUTCOption implements the option interface for the `logtime_utc` setting.
|
||||||
|
type logtimeUTCOption struct {
|
||||||
|
loggingOption
|
||||||
|
newValue bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply is a no-op because logging will be reloaded after options are applied.
|
||||||
|
func (l *logtimeUTCOption) Apply(server *Server) {
|
||||||
|
server.Noticef("Reloaded: logtime_utc = %v", l.newValue)
|
||||||
|
}
|
||||||
|
|
||||||
// logfileOption implements the option interface for the `log_file` setting.
|
// logfileOption implements the option interface for the `log_file` setting.
|
||||||
type logfileOption struct {
|
type logfileOption struct {
|
||||||
loggingOption
|
loggingOption
|
||||||
@@ -609,7 +620,7 @@ func (jso jetStreamOption) IsStatszChange() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ocspOption struct {
|
type ocspOption struct {
|
||||||
noopOption
|
tlsOption
|
||||||
newValue *OCSPConfig
|
newValue *OCSPConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -617,6 +628,15 @@ func (a *ocspOption) Apply(s *Server) {
|
|||||||
s.Noticef("Reloaded: OCSP")
|
s.Noticef("Reloaded: OCSP")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ocspResponseCacheOption struct {
|
||||||
|
tlsOption
|
||||||
|
newValue *OCSPResponseCacheConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ocspResponseCacheOption) Apply(s *Server) {
|
||||||
|
s.Noticef("Reloaded OCSP peer cache")
|
||||||
|
}
|
||||||
|
|
||||||
// connectErrorReports implements the option interface for the `connect_error_reports`
|
// connectErrorReports implements the option interface for the `connect_error_reports`
|
||||||
// setting.
|
// setting.
|
||||||
type connectErrorReports struct {
|
type connectErrorReports struct {
|
||||||
@@ -940,7 +960,7 @@ func imposeOrder(value interface{}) error {
|
|||||||
sort.Strings(value.AllowedOrigins)
|
sort.Strings(value.AllowedOrigins)
|
||||||
case string, bool, uint8, int, int32, int64, time.Duration, float64, nil, LeafNodeOpts, ClusterOpts, *tls.Config, PinnedCertSet,
|
case string, bool, uint8, int, int32, int64, time.Duration, float64, nil, LeafNodeOpts, ClusterOpts, *tls.Config, PinnedCertSet,
|
||||||
*URLAccResolver, *MemAccResolver, *DirAccResolver, *CacheDirAccResolver, Authentication, MQTTOpts, jwt.TagList,
|
*URLAccResolver, *MemAccResolver, *DirAccResolver, *CacheDirAccResolver, Authentication, MQTTOpts, jwt.TagList,
|
||||||
*OCSPConfig, map[string]string, JSLimitOpts, StoreCipher:
|
*OCSPConfig, map[string]string, JSLimitOpts, StoreCipher, *OCSPResponseCacheConfig:
|
||||||
// explicitly skipped types
|
// explicitly skipped types
|
||||||
default:
|
default:
|
||||||
// this will fail during unit tests
|
// this will fail during unit tests
|
||||||
@@ -1009,6 +1029,8 @@ func (s *Server) diffOptions(newOpts *Options) ([]option, error) {
|
|||||||
diffOpts = append(diffOpts, &debugOption{newValue: newValue.(bool)})
|
diffOpts = append(diffOpts, &debugOption{newValue: newValue.(bool)})
|
||||||
case "logtime":
|
case "logtime":
|
||||||
diffOpts = append(diffOpts, &logtimeOption{newValue: newValue.(bool)})
|
diffOpts = append(diffOpts, &logtimeOption{newValue: newValue.(bool)})
|
||||||
|
case "logtimeutc":
|
||||||
|
diffOpts = append(diffOpts, &logtimeUTCOption{newValue: newValue.(bool)})
|
||||||
case "logfile":
|
case "logfile":
|
||||||
diffOpts = append(diffOpts, &logfileOption{newValue: newValue.(string)})
|
diffOpts = append(diffOpts, &logfileOption{newValue: newValue.(string)})
|
||||||
case "syslog":
|
case "syslog":
|
||||||
@@ -1264,8 +1286,8 @@ func (s *Server) diffOptions(newOpts *Options) ([]option, error) {
|
|||||||
// Similar to gateways
|
// Similar to gateways
|
||||||
tmpOld := oldValue.(WebsocketOpts)
|
tmpOld := oldValue.(WebsocketOpts)
|
||||||
tmpNew := newValue.(WebsocketOpts)
|
tmpNew := newValue.(WebsocketOpts)
|
||||||
tmpOld.TLSConfig = nil
|
tmpOld.TLSConfig, tmpOld.tlsConfigOpts = nil, nil
|
||||||
tmpNew.TLSConfig = nil
|
tmpNew.TLSConfig, tmpNew.tlsConfigOpts = nil, nil
|
||||||
// If there is really a change prevents reload.
|
// If there is really a change prevents reload.
|
||||||
if !reflect.DeepEqual(tmpOld, tmpNew) {
|
if !reflect.DeepEqual(tmpOld, tmpNew) {
|
||||||
// See TODO(ik) note below about printing old/new values.
|
// See TODO(ik) note below about printing old/new values.
|
||||||
@@ -1284,9 +1306,9 @@ func (s *Server) diffOptions(newOpts *Options) ([]option, error) {
|
|||||||
// we only fail reload if some that we don't support are changed.
|
// we only fail reload if some that we don't support are changed.
|
||||||
tmpOld := oldValue.(MQTTOpts)
|
tmpOld := oldValue.(MQTTOpts)
|
||||||
tmpNew := newValue.(MQTTOpts)
|
tmpNew := newValue.(MQTTOpts)
|
||||||
tmpOld.TLSConfig, tmpOld.AckWait, tmpOld.MaxAckPending, tmpOld.StreamReplicas, tmpOld.ConsumerReplicas, tmpOld.ConsumerMemoryStorage = nil, 0, 0, 0, 0, false
|
tmpOld.TLSConfig, tmpOld.tlsConfigOpts, tmpOld.AckWait, tmpOld.MaxAckPending, tmpOld.StreamReplicas, tmpOld.ConsumerReplicas, tmpOld.ConsumerMemoryStorage = nil, nil, 0, 0, 0, 0, false
|
||||||
tmpOld.ConsumerInactiveThreshold = 0
|
tmpOld.ConsumerInactiveThreshold = 0
|
||||||
tmpNew.TLSConfig, tmpNew.AckWait, tmpNew.MaxAckPending, tmpNew.StreamReplicas, tmpNew.ConsumerReplicas, tmpNew.ConsumerMemoryStorage = nil, 0, 0, 0, 0, false
|
tmpNew.TLSConfig, tmpNew.tlsConfigOpts, tmpNew.AckWait, tmpNew.MaxAckPending, tmpNew.StreamReplicas, tmpNew.ConsumerReplicas, tmpNew.ConsumerMemoryStorage = nil, nil, 0, 0, 0, 0, false
|
||||||
tmpNew.ConsumerInactiveThreshold = 0
|
tmpNew.ConsumerInactiveThreshold = 0
|
||||||
|
|
||||||
if !reflect.DeepEqual(tmpOld, tmpNew) {
|
if !reflect.DeepEqual(tmpOld, tmpNew) {
|
||||||
@@ -1339,6 +1361,8 @@ func (s *Server) diffOptions(newOpts *Options) ([]option, error) {
|
|||||||
}
|
}
|
||||||
case "ocspconfig":
|
case "ocspconfig":
|
||||||
diffOpts = append(diffOpts, &ocspOption{newValue: newValue.(*OCSPConfig)})
|
diffOpts = append(diffOpts, &ocspOption{newValue: newValue.(*OCSPConfig)})
|
||||||
|
case "ocspcacheconfig":
|
||||||
|
diffOpts = append(diffOpts, &ocspResponseCacheOption{newValue: newValue.(*OCSPResponseCacheConfig)})
|
||||||
default:
|
default:
|
||||||
// TODO(ik): Implement String() on those options to have a nice print.
|
// TODO(ik): Implement String() on those options to have a nice print.
|
||||||
// %v is difficult to figure what's what, %+v print private fields and
|
// %v is difficult to figure what's what, %+v print private fields and
|
||||||
@@ -1476,10 +1500,12 @@ func (s *Server) applyOptions(ctx *reloadContext, opts []option) {
|
|||||||
s.updateRemoteLeafNodesTLSConfig(newOpts)
|
s.updateRemoteLeafNodesTLSConfig(newOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This will fire if TLS enabled at root (NATS listener) -or- if ocsp or ocsp_cache
|
||||||
|
// appear in the config.
|
||||||
if reloadTLS {
|
if reloadTLS {
|
||||||
// Restart OCSP monitoring.
|
// Restart OCSP monitoring.
|
||||||
if err := s.reloadOCSP(); err != nil {
|
if err := s.reloadOCSP(); err != nil {
|
||||||
s.Warnf("Can't restart OCSP Stapling: %v", err)
|
s.Warnf("Can't restart OCSP features: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+67
-16
@@ -246,6 +246,12 @@ type Server struct {
|
|||||||
// OCSP monitoring
|
// OCSP monitoring
|
||||||
ocsps []*OCSPMonitor
|
ocsps []*OCSPMonitor
|
||||||
|
|
||||||
|
// OCSP peer verification (at least one TLS block)
|
||||||
|
ocspPeerVerify bool
|
||||||
|
|
||||||
|
// OCSP response cache
|
||||||
|
ocsprc OCSPResponseCache
|
||||||
|
|
||||||
// exporting account name the importer experienced issues with
|
// exporting account name the importer experienced issues with
|
||||||
incompleteAccExporterMap sync.Map
|
incompleteAccExporterMap sync.Map
|
||||||
|
|
||||||
@@ -453,8 +459,8 @@ func NewServer(opts *Options) (*Server, error) {
|
|||||||
// Ensure that non-exported options (used in tests) are properly set.
|
// Ensure that non-exported options (used in tests) are properly set.
|
||||||
s.setLeafNodeNonExportedOptions()
|
s.setLeafNodeNonExportedOptions()
|
||||||
|
|
||||||
// Setup OCSP Stapling. This will abort server from starting if there
|
// Setup OCSP Stapling and OCSP Peer. This will abort server from starting if there
|
||||||
// are no valid staples and OCSP policy is set to Always or MustStaple.
|
// are no valid staples and OCSP Stapling policy is set to Always or MustStaple.
|
||||||
if err := s.enableOCSP(); err != nil {
|
if err := s.enableOCSP(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -753,6 +759,12 @@ func (s *Server) configureAccounts(reloading bool) (map[string]struct{}, error)
|
|||||||
|
|
||||||
opts := s.getOpts()
|
opts := s.getOpts()
|
||||||
|
|
||||||
|
// We need to track service imports since we can not swap them out (unsub and re-sub)
|
||||||
|
// until the proper server struct accounts have been swapped in properly. Doing it in
|
||||||
|
// place could lead to data loss or server panic since account under new si has no real
|
||||||
|
// account and hence no sublist, so will panic on inbound message.
|
||||||
|
siMap := make(map[*Account][][]byte)
|
||||||
|
|
||||||
// Check opts and walk through them. We need to copy them here
|
// Check opts and walk through them. We need to copy them here
|
||||||
// so that we do not keep a real one sitting in the options.
|
// so that we do not keep a real one sitting in the options.
|
||||||
for _, acc := range opts.Accounts {
|
for _, acc := range opts.Accounts {
|
||||||
@@ -773,12 +785,16 @@ func (s *Server) configureAccounts(reloading bool) (map[string]struct{}, error)
|
|||||||
// Collect the sids for the service imports since we are going to
|
// Collect the sids for the service imports since we are going to
|
||||||
// replace with new ones.
|
// replace with new ones.
|
||||||
var sids [][]byte
|
var sids [][]byte
|
||||||
c := a.ic
|
|
||||||
for _, si := range a.imports.services {
|
for _, si := range a.imports.services {
|
||||||
if c != nil && si.sid != nil {
|
if si.sid != nil {
|
||||||
sids = append(sids, si.sid)
|
sids = append(sids, si.sid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Setup to process later if needed.
|
||||||
|
if len(sids) > 0 || len(acc.imports.services) > 0 {
|
||||||
|
siMap[a] = sids
|
||||||
|
}
|
||||||
|
|
||||||
// Now reset all export/imports fields since they are going to be
|
// Now reset all export/imports fields since they are going to be
|
||||||
// filled in shallowCopy()
|
// filled in shallowCopy()
|
||||||
a.imports.streams, a.imports.services = nil, nil
|
a.imports.streams, a.imports.services = nil, nil
|
||||||
@@ -787,14 +803,6 @@ func (s *Server) configureAccounts(reloading bool) (map[string]struct{}, error)
|
|||||||
// and pass `a` (our existing account) to get it updated.
|
// and pass `a` (our existing account) to get it updated.
|
||||||
acc.shallowCopy(a)
|
acc.shallowCopy(a)
|
||||||
a.mu.Unlock()
|
a.mu.Unlock()
|
||||||
// Need to release the lock for this.
|
|
||||||
s.mu.Unlock()
|
|
||||||
for _, sid := range sids {
|
|
||||||
c.processUnsub(sid)
|
|
||||||
}
|
|
||||||
// Add subscriptions for existing service imports.
|
|
||||||
a.addAllServiceImportSubs()
|
|
||||||
s.mu.Lock()
|
|
||||||
create = false
|
create = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -862,6 +870,7 @@ func (s *Server) configureAccounts(reloading bool) (map[string]struct{}, error)
|
|||||||
for _, si := range acc.imports.services {
|
for _, si := range acc.imports.services {
|
||||||
if v, ok := s.accounts.Load(si.acc.Name); ok {
|
if v, ok := s.accounts.Load(si.acc.Name); ok {
|
||||||
si.acc = v.(*Account)
|
si.acc = v.(*Account)
|
||||||
|
|
||||||
// It is possible to allow for latency tracking inside your
|
// It is possible to allow for latency tracking inside your
|
||||||
// own account, so lock only when not the same account.
|
// own account, so lock only when not the same account.
|
||||||
if si.acc == acc {
|
if si.acc == acc {
|
||||||
@@ -889,6 +898,19 @@ func (s *Server) configureAccounts(reloading bool) (map[string]struct{}, error)
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Check if we need to process service imports pending from above.
|
||||||
|
// This processing needs to be after we swap in the real accounts above.
|
||||||
|
for acc, sids := range siMap {
|
||||||
|
c := acc.ic
|
||||||
|
for _, sid := range sids {
|
||||||
|
c.processUnsub(sid)
|
||||||
|
}
|
||||||
|
acc.addAllServiceImportSubs()
|
||||||
|
s.mu.Unlock()
|
||||||
|
s.registerSystemImports(acc)
|
||||||
|
s.mu.Lock()
|
||||||
|
}
|
||||||
|
|
||||||
// Set the system account if it was configured.
|
// Set the system account if it was configured.
|
||||||
// Otherwise create a default one.
|
// Otherwise create a default one.
|
||||||
if opts.SystemAccount != _EMPTY_ {
|
if opts.SystemAccount != _EMPTY_ {
|
||||||
@@ -1888,9 +1910,13 @@ func (s *Server) Start() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start OCSP Stapling monitoring for TLS certificates if enabled.
|
// Start OCSP Stapling monitoring for TLS certificates if enabled. Hook TLS handshake for
|
||||||
|
// OCSP check on peers (LEAF and CLIENT kind) if enabled.
|
||||||
s.startOCSPMonitoring()
|
s.startOCSPMonitoring()
|
||||||
|
|
||||||
|
// Configure OCSP Response Cache for peer OCSP checks if enabled.
|
||||||
|
s.initOCSPResponseCache()
|
||||||
|
|
||||||
// Start up gateway if needed. Do this before starting the routes, because
|
// Start up gateway if needed. Do this before starting the routes, because
|
||||||
// we want to resolve the gateway host:port so that this information can
|
// we want to resolve the gateway host:port so that this information can
|
||||||
// be sent to other routes.
|
// be sent to other routes.
|
||||||
@@ -1957,6 +1983,9 @@ func (s *Server) Start() {
|
|||||||
if !opts.DontListen {
|
if !opts.DontListen {
|
||||||
s.AcceptLoop(clientListenReady)
|
s.AcceptLoop(clientListenReady)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bring OSCP Response cache online after accept loop started in anticipation of NATS-enabled cache types
|
||||||
|
s.startOCSPResponseCache()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown will shutdown the server instance by kicking out the AcceptLoop
|
// Shutdown will shutdown the server instance by kicking out the AcceptLoop
|
||||||
@@ -2117,6 +2146,12 @@ func (s *Server) Shutdown() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s.Noticef("Server Exiting..")
|
s.Noticef("Server Exiting..")
|
||||||
|
|
||||||
|
// Stop OCSP Response Cache
|
||||||
|
if s.ocsprc != nil {
|
||||||
|
s.ocsprc.Stop(s)
|
||||||
|
}
|
||||||
|
|
||||||
// Close logger if applicable. It allows tests on Windows
|
// Close logger if applicable. It allows tests on Windows
|
||||||
// to be able to do proper cleanup (delete log file).
|
// to be able to do proper cleanup (delete log file).
|
||||||
s.logging.RLock()
|
s.logging.RLock()
|
||||||
@@ -2217,7 +2252,7 @@ func (s *Server) AcceptLoop(clr chan struct{}) {
|
|||||||
func (s *Server) InProcessConn() (net.Conn, error) {
|
func (s *Server) InProcessConn() (net.Conn, error) {
|
||||||
pl, pr := net.Pipe()
|
pl, pr := net.Pipe()
|
||||||
if !s.startGoRoutine(func() {
|
if !s.startGoRoutine(func() {
|
||||||
s.createClient(pl)
|
s.createClientInProcess(pl)
|
||||||
s.grWG.Done()
|
s.grWG.Done()
|
||||||
}) {
|
}) {
|
||||||
pl.Close()
|
pl.Close()
|
||||||
@@ -2572,6 +2607,14 @@ func (c *tlsMixConn) Read(b []byte) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) createClient(conn net.Conn) *client {
|
func (s *Server) createClient(conn net.Conn) *client {
|
||||||
|
return s.createClientEx(conn, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) createClientInProcess(conn net.Conn) *client {
|
||||||
|
return s.createClientEx(conn, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) createClientEx(conn net.Conn, inProcess bool) *client {
|
||||||
// Snapshot server options.
|
// Snapshot server options.
|
||||||
opts := s.getOpts()
|
opts := s.getOpts()
|
||||||
|
|
||||||
@@ -2609,6 +2652,13 @@ func (s *Server) createClient(conn net.Conn) *client {
|
|||||||
info.AuthRequired = false
|
info.AuthRequired = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check to see if this is an in-process connection with tls_required.
|
||||||
|
// If so, set as not required, but available.
|
||||||
|
if inProcess && info.TLSRequired {
|
||||||
|
info.TLSRequired = false
|
||||||
|
info.TLSAvailable = true
|
||||||
|
}
|
||||||
|
|
||||||
s.totalClients++
|
s.totalClients++
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
|
|
||||||
@@ -2670,8 +2720,9 @@ func (s *Server) createClient(conn net.Conn) *client {
|
|||||||
|
|
||||||
var pre []byte
|
var pre []byte
|
||||||
// If we have both TLS and non-TLS allowed we need to see which
|
// If we have both TLS and non-TLS allowed we need to see which
|
||||||
// one the client wants.
|
// one the client wants. We'll always allow this for in-process
|
||||||
if !isClosed && opts.TLSConfig != nil && opts.AllowNonTLS {
|
// connections.
|
||||||
|
if !isClosed && opts.TLSConfig != nil && (inProcess || opts.AllowNonTLS) {
|
||||||
pre = make([]byte, 4)
|
pre = make([]byte, 4)
|
||||||
c.nc.SetReadDeadline(time.Now().Add(secondsToDuration(opts.TLSTimeout)))
|
c.nc.SetReadDeadline(time.Now().Add(secondsToDuration(opts.TLSTimeout)))
|
||||||
n, _ := io.ReadFull(c.nc, pre[:])
|
n, _ := io.ReadFull(c.nc, pre[:])
|
||||||
|
|||||||
+13
-5
@@ -838,6 +838,14 @@ func (mset *stream) lastSeqAndCLFS() (uint64, uint64) {
|
|||||||
return mset.lseq, mset.clfs
|
return mset.lseq, mset.clfs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mset *stream) clearCLFS() uint64 {
|
||||||
|
mset.mu.Lock()
|
||||||
|
defer mset.mu.Unlock()
|
||||||
|
clfs := mset.clfs
|
||||||
|
mset.clfs, mset.clseq = 0, 0
|
||||||
|
return clfs
|
||||||
|
}
|
||||||
|
|
||||||
func (mset *stream) lastSeq() uint64 {
|
func (mset *stream) lastSeq() uint64 {
|
||||||
mset.mu.RLock()
|
mset.mu.RLock()
|
||||||
lseq := mset.lseq
|
lseq := mset.lseq
|
||||||
@@ -2093,7 +2101,7 @@ func (mset *stream) processInboundMirrorMsg(m *inMsg) bool {
|
|||||||
var err error
|
var err error
|
||||||
if node != nil {
|
if node != nil {
|
||||||
if js.limitsExceeded(stype) {
|
if js.limitsExceeded(stype) {
|
||||||
s.resourcesExeededError()
|
s.resourcesExceededError()
|
||||||
err = ApiErrors[JSInsufficientResourcesErr]
|
err = ApiErrors[JSInsufficientResourcesErr]
|
||||||
} else {
|
} else {
|
||||||
err = node.Propose(encodeStreamMsg(m.subj, _EMPTY_, m.hdr, m.msg, sseq-1, ts))
|
err = node.Propose(encodeStreamMsg(m.subj, _EMPTY_, m.hdr, m.msg, sseq-1, ts))
|
||||||
@@ -3364,9 +3372,9 @@ func (mset *stream) setupStore(fsCfg *FileStoreConfig) error {
|
|||||||
// Register our server.
|
// Register our server.
|
||||||
fs.registerServer(s)
|
fs.registerServer(s)
|
||||||
}
|
}
|
||||||
mset.mu.Unlock()
|
// This will fire the callback but we do not require the lock since md will be 0 here.
|
||||||
|
|
||||||
mset.store.RegisterStorageUpdates(mset.storeUpdates)
|
mset.store.RegisterStorageUpdates(mset.storeUpdates)
|
||||||
|
mset.mu.Unlock()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -3838,7 +3846,7 @@ func (mset *stream) processJetStreamMsg(subject, reply string, hdr, msg []byte,
|
|||||||
}
|
}
|
||||||
// Expected last sequence per subject.
|
// Expected last sequence per subject.
|
||||||
// If we are clustered we have prechecked seq > 0.
|
// If we are clustered we have prechecked seq > 0.
|
||||||
if seq, exists := getExpectedLastSeqPerSubject(hdr); exists && (!isClustered || seq == 0) {
|
if seq, exists := getExpectedLastSeqPerSubject(hdr); exists {
|
||||||
// TODO(dlc) - We could make a new store func that does this all in one.
|
// TODO(dlc) - We could make a new store func that does this all in one.
|
||||||
var smv StoreMsg
|
var smv StoreMsg
|
||||||
var fseq uint64
|
var fseq uint64
|
||||||
@@ -3952,7 +3960,7 @@ func (mset *stream) processJetStreamMsg(subject, reply string, hdr, msg []byte,
|
|||||||
|
|
||||||
// Check to see if we have exceeded our limits.
|
// Check to see if we have exceeded our limits.
|
||||||
if js.limitsExceeded(stype) {
|
if js.limitsExceeded(stype) {
|
||||||
s.resourcesExeededError()
|
s.resourcesExceededError()
|
||||||
mset.clfs++
|
mset.clfs++
|
||||||
mset.mu.Unlock()
|
mset.mu.Unlock()
|
||||||
if canRespond {
|
if canRespond {
|
||||||
|
|||||||
+1
-1
@@ -48,7 +48,7 @@ const (
|
|||||||
// cacheMax is used to bound limit the frontend cache
|
// cacheMax is used to bound limit the frontend cache
|
||||||
slCacheMax = 1024
|
slCacheMax = 1024
|
||||||
// If we run a sweeper we will drain to this count.
|
// If we run a sweeper we will drain to this count.
|
||||||
slCacheSweep = 512
|
slCacheSweep = 256
|
||||||
// plistMin is our lower bounds to create a fast plist for Match.
|
// plistMin is our lower bounds to create a fast plist for Match.
|
||||||
plistMin = 256
|
plistMin = 256
|
||||||
)
|
)
|
||||||
|
|||||||
+14
@@ -25,3 +25,17 @@ script:
|
|||||||
- if [[ "$TRAVIS_GO_VERSION" =~ 1.20 ]]; then ./scripts/cov.sh TRAVIS; else go test -modfile=go_test.mod -race -v -p=1 ./... --failfast -vet=off; fi
|
- if [[ "$TRAVIS_GO_VERSION" =~ 1.20 ]]; then ./scripts/cov.sh TRAVIS; else go test -modfile=go_test.mod -race -v -p=1 ./... --failfast -vet=off; fi
|
||||||
after_success:
|
after_success:
|
||||||
- if [[ "$TRAVIS_GO_VERSION" =~ 1.20 ]]; then $HOME/gopath/bin/goveralls -coverprofile=acc.out -service travis-ci; fi
|
- if [[ "$TRAVIS_GO_VERSION" =~ 1.20 ]]; then $HOME/gopath/bin/goveralls -coverprofile=acc.out -service travis-ci; fi
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
include:
|
||||||
|
- name: "Go: 1.20.x (nats-server@dev)"
|
||||||
|
go: "1.20.x"
|
||||||
|
before_script:
|
||||||
|
- go get -modfile go_test.mod github.com/nats-io/nats-server/v2@dev
|
||||||
|
- name: "Go: 1.20.x (nats-server@main)"
|
||||||
|
go: "1.20.x"
|
||||||
|
before_script:
|
||||||
|
- go get -modfile go_test.mod github.com/nats-io/nats-server/v2@main
|
||||||
|
allow_failures:
|
||||||
|
- name: "Go: 1.20.x (nats-server@dev)"
|
||||||
|
- name: "Go: 1.20.x (nats-server@main)"
|
||||||
|
|||||||
+28
-72
@@ -29,7 +29,7 @@ When using or transitioning to Go modules support:
|
|||||||
```bash
|
```bash
|
||||||
# Go client latest or explicit version
|
# Go client latest or explicit version
|
||||||
go get github.com/nats-io/nats.go/@latest
|
go get github.com/nats-io/nats.go/@latest
|
||||||
go get github.com/nats-io/nats.go/@v1.27.0
|
go get github.com/nats-io/nats.go/@v1.28.0
|
||||||
|
|
||||||
# For latest NATS Server, add /v2 at the end
|
# For latest NATS Server, add /v2 at the end
|
||||||
go get github.com/nats-io/nats-server/v2
|
go get github.com/nats-io/nats-server/v2
|
||||||
@@ -90,91 +90,47 @@ nc.Drain()
|
|||||||
nc.Close()
|
nc.Close()
|
||||||
```
|
```
|
||||||
|
|
||||||
## JetStream Basic Usage
|
## JetStream
|
||||||
|
|
||||||
> __NOTE__
|
JetStream is the built-in NATS persistence system. `nats.go` provides a built-in
|
||||||
>
|
API enabling both managing JetStream assets as well as publishing/consuming
|
||||||
> We encourage you to try out a new, simplified version on JetStream API.
|
persistent messages.
|
||||||
> The new API is currently in preview and is available under `jetstream` package.
|
|
||||||
>
|
### Basic usage
|
||||||
> You can find more information on the new API [here](https://github.com/nats-io/nats.go/blob/main/jetstream/README.md)
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "github.com/nats-io/nats.go"
|
// connect to nats server
|
||||||
|
|
||||||
// Connect to NATS
|
|
||||||
nc, _ := nats.Connect(nats.DefaultURL)
|
nc, _ := nats.Connect(nats.DefaultURL)
|
||||||
|
|
||||||
// Create JetStream Context
|
// create jetstream context from nats connection
|
||||||
js, _ := nc.JetStream(nats.PublishAsyncMaxPending(256))
|
js, _ := jetstream.New(nc)
|
||||||
|
|
||||||
// Simple Stream Publisher
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
js.Publish("ORDERS.scratch", []byte("hello"))
|
defer cancel()
|
||||||
|
|
||||||
// Simple Async Stream Publisher
|
// get existing stream handle
|
||||||
for i := 0; i < 500; i++ {
|
stream, _ := js.Stream(ctx, "foo")
|
||||||
js.PublishAsync("ORDERS.scratch", []byte("hello"))
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-js.PublishAsyncComplete():
|
|
||||||
case <-time.After(5 * time.Second):
|
|
||||||
fmt.Println("Did not resolve in time")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simple Async Ephemeral Consumer
|
// retrieve consumer handle from a stream
|
||||||
js.Subscribe("ORDERS.*", func(m *nats.Msg) {
|
cons, _ := stream.Consumer(ctx, "cons")
|
||||||
fmt.Printf("Received a JetStream message: %s\n", string(m.Data))
|
|
||||||
|
// consume messages from the consumer in callback
|
||||||
|
cc, _ := cons.Consume(func(msg jetstream.Msg) {
|
||||||
|
fmt.Println("Received jetstream message: ", string(msg.Data()))
|
||||||
|
msg.Ack()
|
||||||
})
|
})
|
||||||
|
defer cc.Stop()
|
||||||
// Simple Sync Durable Consumer (optional SubOpts at the end)
|
|
||||||
sub, err := js.SubscribeSync("ORDERS.*", nats.Durable("MONITOR"), nats.MaxDeliver(3))
|
|
||||||
m, err := sub.NextMsg(timeout)
|
|
||||||
|
|
||||||
// Simple Pull Consumer
|
|
||||||
sub, err := js.PullSubscribe("ORDERS.*", "MONITOR")
|
|
||||||
msgs, err := sub.Fetch(10)
|
|
||||||
|
|
||||||
// Unsubscribe
|
|
||||||
sub.Unsubscribe()
|
|
||||||
|
|
||||||
// Drain
|
|
||||||
sub.Drain()
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## JetStream Basic Management
|
To find more information on `nats.go` JetStream API, visit
|
||||||
|
[`jetstream/README.md`](jetstream/README.md)
|
||||||
|
|
||||||
```go
|
> The current JetStream API replaces the [legacy JetStream API](legacy_jetstream.md)
|
||||||
import "github.com/nats-io/nats.go"
|
|
||||||
|
|
||||||
// Connect to NATS
|
## Service API
|
||||||
nc, _ := nats.Connect(nats.DefaultURL)
|
|
||||||
|
|
||||||
// Create JetStream Context
|
The service API (`micro`) allows you to [easily build NATS services](micro/README.md) The
|
||||||
js, _ := nc.JetStream()
|
services API is currently in beta release.
|
||||||
|
|
||||||
// Create a Stream
|
|
||||||
js.AddStream(&nats.StreamConfig{
|
|
||||||
Name: "ORDERS",
|
|
||||||
Subjects: []string{"ORDERS.*"},
|
|
||||||
})
|
|
||||||
|
|
||||||
// Update a Stream
|
|
||||||
js.UpdateStream(&nats.StreamConfig{
|
|
||||||
Name: "ORDERS",
|
|
||||||
MaxBytes: 8,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Create a Consumer
|
|
||||||
js.AddConsumer("ORDERS", &nats.ConsumerConfig{
|
|
||||||
Durable: "MONITOR",
|
|
||||||
})
|
|
||||||
|
|
||||||
// Delete Consumer
|
|
||||||
js.DeleteConsumer("ORDERS", "MONITOR")
|
|
||||||
|
|
||||||
// Delete Stream
|
|
||||||
js.DeleteStream("ORDERS")
|
|
||||||
```
|
|
||||||
|
|
||||||
## Encoded Connections
|
## Encoded Connections
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -217,7 +217,7 @@ func (nc *Conn) FlushWithContext(ctx context.Context) error {
|
|||||||
// RequestWithContext will create an Inbox and perform a Request
|
// RequestWithContext will create an Inbox and perform a Request
|
||||||
// using the provided cancellation context with the Inbox reply
|
// using the provided cancellation context with the Inbox reply
|
||||||
// for the data v. A response will be decoded into the vPtr last parameter.
|
// for the data v. A response will be decoded into the vPtr last parameter.
|
||||||
func (c *EncodedConn) RequestWithContext(ctx context.Context, subject string, v interface{}, vPtr interface{}) error {
|
func (c *EncodedConn) RequestWithContext(ctx context.Context, subject string, v any, vPtr any) error {
|
||||||
if ctx == nil {
|
if ctx == nil {
|
||||||
return ErrInvalidContext
|
return ErrInvalidContext
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-7
@@ -26,8 +26,8 @@ import (
|
|||||||
|
|
||||||
// Encoder interface is for all register encoders
|
// Encoder interface is for all register encoders
|
||||||
type Encoder interface {
|
type Encoder interface {
|
||||||
Encode(subject string, v interface{}) ([]byte, error)
|
Encode(subject string, v any) ([]byte, error)
|
||||||
Decode(subject string, data []byte, vPtr interface{}) error
|
Decode(subject string, data []byte, vPtr any) error
|
||||||
}
|
}
|
||||||
|
|
||||||
var encMap map[string]Encoder
|
var encMap map[string]Encoder
|
||||||
@@ -88,7 +88,7 @@ func EncoderForType(encType string) Encoder {
|
|||||||
|
|
||||||
// Publish publishes the data argument to the given subject. The data argument
|
// Publish publishes the data argument to the given subject. The data argument
|
||||||
// will be encoded using the associated encoder.
|
// will be encoded using the associated encoder.
|
||||||
func (c *EncodedConn) Publish(subject string, v interface{}) error {
|
func (c *EncodedConn) Publish(subject string, v any) error {
|
||||||
b, err := c.Enc.Encode(subject, v)
|
b, err := c.Enc.Encode(subject, v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -99,7 +99,7 @@ func (c *EncodedConn) Publish(subject string, v interface{}) error {
|
|||||||
// PublishRequest will perform a Publish() expecting a response on the
|
// PublishRequest will perform a Publish() expecting a response on the
|
||||||
// reply subject. Use Request() for automatically waiting for a response
|
// reply subject. Use Request() for automatically waiting for a response
|
||||||
// inline.
|
// inline.
|
||||||
func (c *EncodedConn) PublishRequest(subject, reply string, v interface{}) error {
|
func (c *EncodedConn) PublishRequest(subject, reply string, v any) error {
|
||||||
b, err := c.Enc.Encode(subject, v)
|
b, err := c.Enc.Encode(subject, v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -110,7 +110,7 @@ func (c *EncodedConn) PublishRequest(subject, reply string, v interface{}) error
|
|||||||
// Request will create an Inbox and perform a Request() call
|
// Request will create an Inbox and perform a Request() call
|
||||||
// with the Inbox reply for the data v. A response will be
|
// with the Inbox reply for the data v. A response will be
|
||||||
// decoded into the vPtr Response.
|
// decoded into the vPtr Response.
|
||||||
func (c *EncodedConn) Request(subject string, v interface{}, vPtr interface{}, timeout time.Duration) error {
|
func (c *EncodedConn) Request(subject string, v any, vPtr any, timeout time.Duration) error {
|
||||||
b, err := c.Enc.Encode(subject, v)
|
b, err := c.Enc.Encode(subject, v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -129,7 +129,7 @@ func (c *EncodedConn) Request(subject string, v interface{}, vPtr interface{}, t
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handler is a specific callback used for Subscribe. It is generalized to
|
// Handler is a specific callback used for Subscribe. It is generalized to
|
||||||
// an interface{}, but we will discover its format and arguments at runtime
|
// an any, but we will discover its format and arguments at runtime
|
||||||
// and perform the correct callback, including demarshaling encoded data
|
// and perform the correct callback, including demarshaling encoded data
|
||||||
// back into the appropriate struct based on the signature of the Handler.
|
// back into the appropriate struct based on the signature of the Handler.
|
||||||
//
|
//
|
||||||
@@ -150,7 +150,7 @@ func (c *EncodedConn) Request(subject string, v interface{}, vPtr interface{}, t
|
|||||||
// and demarshal it into the given struct, e.g. person.
|
// and demarshal it into the given struct, e.g. person.
|
||||||
// There are also variants where the callback wants either the subject, or the
|
// There are also variants where the callback wants either the subject, or the
|
||||||
// subject and the reply subject.
|
// subject and the reply subject.
|
||||||
type Handler interface{}
|
type Handler any
|
||||||
|
|
||||||
// Dissect the cb Handler's signature
|
// Dissect the cb Handler's signature
|
||||||
func argInfo(cb Handler) (reflect.Type, int) {
|
func argInfo(cb Handler) (reflect.Type, int) {
|
||||||
|
|||||||
+2
-2
@@ -35,7 +35,7 @@ var falseB = []byte("false")
|
|||||||
var nilB = []byte("")
|
var nilB = []byte("")
|
||||||
|
|
||||||
// Encode
|
// Encode
|
||||||
func (je *DefaultEncoder) Encode(subject string, v interface{}) ([]byte, error) {
|
func (je *DefaultEncoder) Encode(subject string, v any) ([]byte, error) {
|
||||||
switch arg := v.(type) {
|
switch arg := v.(type) {
|
||||||
case string:
|
case string:
|
||||||
bytes := *(*[]byte)(unsafe.Pointer(&arg))
|
bytes := *(*[]byte)(unsafe.Pointer(&arg))
|
||||||
@@ -58,7 +58,7 @@ func (je *DefaultEncoder) Encode(subject string, v interface{}) ([]byte, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Decode
|
// Decode
|
||||||
func (je *DefaultEncoder) Decode(subject string, data []byte, vPtr interface{}) error {
|
func (je *DefaultEncoder) Decode(subject string, data []byte, vPtr any) error {
|
||||||
// Figure out what it's pointing to...
|
// Figure out what it's pointing to...
|
||||||
sData := *(*string)(unsafe.Pointer(&data))
|
sData := *(*string)(unsafe.Pointer(&data))
|
||||||
switch arg := vPtr.(type) {
|
switch arg := vPtr.(type) {
|
||||||
|
|||||||
+2
-2
@@ -28,7 +28,7 @@ type GobEncoder struct {
|
|||||||
// FIXME(dlc) - This could probably be more efficient.
|
// FIXME(dlc) - This could probably be more efficient.
|
||||||
|
|
||||||
// Encode
|
// Encode
|
||||||
func (ge *GobEncoder) Encode(subject string, v interface{}) ([]byte, error) {
|
func (ge *GobEncoder) Encode(subject string, v any) ([]byte, error) {
|
||||||
b := new(bytes.Buffer)
|
b := new(bytes.Buffer)
|
||||||
enc := gob.NewEncoder(b)
|
enc := gob.NewEncoder(b)
|
||||||
if err := enc.Encode(v); err != nil {
|
if err := enc.Encode(v); err != nil {
|
||||||
@@ -38,7 +38,7 @@ func (ge *GobEncoder) Encode(subject string, v interface{}) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Decode
|
// Decode
|
||||||
func (ge *GobEncoder) Decode(subject string, data []byte, vPtr interface{}) (err error) {
|
func (ge *GobEncoder) Decode(subject string, data []byte, vPtr any) (err error) {
|
||||||
dec := gob.NewDecoder(bytes.NewBuffer(data))
|
dec := gob.NewDecoder(bytes.NewBuffer(data))
|
||||||
err = dec.Decode(vPtr)
|
err = dec.Decode(vPtr)
|
||||||
return
|
return
|
||||||
|
|||||||
+2
-2
@@ -26,7 +26,7 @@ type JsonEncoder struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Encode
|
// Encode
|
||||||
func (je *JsonEncoder) Encode(subject string, v interface{}) ([]byte, error) {
|
func (je *JsonEncoder) Encode(subject string, v any) ([]byte, error) {
|
||||||
b, err := json.Marshal(v)
|
b, err := json.Marshal(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -35,7 +35,7 @@ func (je *JsonEncoder) Encode(subject string, v interface{}) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Decode
|
// Decode
|
||||||
func (je *JsonEncoder) Decode(subject string, data []byte, vPtr interface{}) (err error) {
|
func (je *JsonEncoder) Decode(subject string, data []byte, vPtr any) (err error) {
|
||||||
switch arg := vPtr.(type) {
|
switch arg := vPtr.(type) {
|
||||||
case *string:
|
case *string:
|
||||||
// If they want a string and it is a JSON string, strip quotes
|
// If they want a string and it is a JSON string, strip quotes
|
||||||
|
|||||||
+3
-3
@@ -5,7 +5,7 @@ go 1.19
|
|||||||
require (
|
require (
|
||||||
github.com/golang/protobuf v1.4.2
|
github.com/golang/protobuf v1.4.2
|
||||||
github.com/klauspost/compress v1.16.5
|
github.com/klauspost/compress v1.16.5
|
||||||
github.com/nats-io/nats-server/v2 v2.9.16
|
github.com/nats-io/nats-server/v2 v2.9.19
|
||||||
github.com/nats-io/nkeys v0.4.4
|
github.com/nats-io/nkeys v0.4.4
|
||||||
github.com/nats-io/nuid v1.0.1
|
github.com/nats-io/nuid v1.0.1
|
||||||
go.uber.org/goleak v1.2.1
|
go.uber.org/goleak v1.2.1
|
||||||
@@ -16,7 +16,7 @@ require (
|
|||||||
require (
|
require (
|
||||||
github.com/minio/highwayhash v1.0.2 // indirect
|
github.com/minio/highwayhash v1.0.2 // indirect
|
||||||
github.com/nats-io/jwt/v2 v2.4.1 // indirect
|
github.com/nats-io/jwt/v2 v2.4.1 // indirect
|
||||||
golang.org/x/crypto v0.8.0 // indirect
|
golang.org/x/crypto v0.9.0 // indirect
|
||||||
golang.org/x/sys v0.7.0 // indirect
|
golang.org/x/sys v0.8.0 // indirect
|
||||||
golang.org/x/time v0.3.0 // indirect
|
golang.org/x/time v0.3.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
+6
-6
@@ -16,8 +16,8 @@ github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA
|
|||||||
github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
|
github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
|
||||||
github.com/nats-io/jwt/v2 v2.4.1 h1:Y35W1dgbbz2SQUYDPCaclXcuqleVmpbRa7646Jf2EX4=
|
github.com/nats-io/jwt/v2 v2.4.1 h1:Y35W1dgbbz2SQUYDPCaclXcuqleVmpbRa7646Jf2EX4=
|
||||||
github.com/nats-io/jwt/v2 v2.4.1/go.mod h1:24BeQtRwxRV8ruvC4CojXlx/WQ/VjuwlYiH+vu/+ibI=
|
github.com/nats-io/jwt/v2 v2.4.1/go.mod h1:24BeQtRwxRV8ruvC4CojXlx/WQ/VjuwlYiH+vu/+ibI=
|
||||||
github.com/nats-io/nats-server/v2 v2.9.16 h1:SuNe6AyCcVy0g5326wtyU8TdqYmcPqzTjhkHojAjprc=
|
github.com/nats-io/nats-server/v2 v2.9.19 h1:OF9jSKZGo425C/FcVVIvNgpd36CUe7aVTTXEZRJk6kA=
|
||||||
github.com/nats-io/nats-server/v2 v2.9.16/go.mod h1:z1cc5Q+kqJkz9mLUdlcSsdYnId4pyImHjNgoh6zxSC0=
|
github.com/nats-io/nats-server/v2 v2.9.19/go.mod h1:aTb/xtLCGKhfTFLxP591CMWfkdgBmcUUSkiSOe5A3gw=
|
||||||
github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA=
|
github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA=
|
||||||
github.com/nats-io/nkeys v0.4.4/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64=
|
github.com/nats-io/nkeys v0.4.4/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64=
|
||||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||||
@@ -26,11 +26,11 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||||
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
||||||
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
||||||
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
|
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
||||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||||
golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||||
|
|||||||
+38
-8
@@ -227,13 +227,14 @@ type js struct {
|
|||||||
opts *jsOpts
|
opts *jsOpts
|
||||||
|
|
||||||
// For async publish context.
|
// For async publish context.
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
rpre string
|
rpre string
|
||||||
rsub *Subscription
|
rsub *Subscription
|
||||||
pafs map[string]*pubAckFuture
|
pafs map[string]*pubAckFuture
|
||||||
stc chan struct{}
|
stc chan struct{}
|
||||||
dch chan struct{}
|
dch chan struct{}
|
||||||
rr *rand.Rand
|
rr *rand.Rand
|
||||||
|
connStatusCh chan (Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
type jsOpts struct {
|
type jsOpts struct {
|
||||||
@@ -666,6 +667,10 @@ func (js *js) newAsyncReply() string {
|
|||||||
js.rsub = sub
|
js.rsub = sub
|
||||||
js.rr = rand.New(rand.NewSource(time.Now().UnixNano()))
|
js.rr = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
}
|
}
|
||||||
|
if js.connStatusCh == nil {
|
||||||
|
js.connStatusCh = js.nc.StatusChanged(RECONNECTING, CLOSED)
|
||||||
|
go js.resetPendingAcksOnReconnect()
|
||||||
|
}
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
sb.WriteString(js.rpre)
|
sb.WriteString(js.rpre)
|
||||||
rn := js.rr.Int63()
|
rn := js.rr.Int63()
|
||||||
@@ -679,12 +684,34 @@ func (js *js) newAsyncReply() string {
|
|||||||
return sb.String()
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (js *js) resetPendingAcksOnReconnect() {
|
||||||
|
js.mu.Lock()
|
||||||
|
connStatusCh := js.connStatusCh
|
||||||
|
js.mu.Unlock()
|
||||||
|
for {
|
||||||
|
newStatus, ok := <-connStatusCh
|
||||||
|
if !ok || newStatus == CLOSED {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
js.mu.Lock()
|
||||||
|
for _, paf := range js.pafs {
|
||||||
|
paf.err = ErrDisconnected
|
||||||
|
}
|
||||||
|
js.pafs = nil
|
||||||
|
js.mu.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (js *js) cleanupReplySub() {
|
func (js *js) cleanupReplySub() {
|
||||||
js.mu.Lock()
|
js.mu.Lock()
|
||||||
if js.rsub != nil {
|
if js.rsub != nil {
|
||||||
js.rsub.Unsubscribe()
|
js.rsub.Unsubscribe()
|
||||||
js.rsub = nil
|
js.rsub = nil
|
||||||
}
|
}
|
||||||
|
if js.connStatusCh != nil {
|
||||||
|
close(js.connStatusCh)
|
||||||
|
js.connStatusCh = nil
|
||||||
|
}
|
||||||
js.mu.Unlock()
|
js.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1352,7 +1379,7 @@ func processConsInfo(info *ConsumerInfo, userCfg *ConsumerConfig, isPullMode boo
|
|||||||
}
|
}
|
||||||
|
|
||||||
func checkConfig(s, u *ConsumerConfig) error {
|
func checkConfig(s, u *ConsumerConfig) error {
|
||||||
makeErr := func(fieldName string, usrVal, srvVal interface{}) error {
|
makeErr := func(fieldName string, usrVal, srvVal any) error {
|
||||||
return fmt.Errorf("configuration requests %s to be %v, but consumer's value is %v", fieldName, usrVal, srvVal)
|
return fmt.Errorf("configuration requests %s to be %v, but consumer's value is %v", fieldName, usrVal, srvVal)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1991,6 +2018,9 @@ func (sub *Subscription) resetOrderedConsumer(sseq uint64) {
|
|||||||
cfg.DeliverSubject = newDeliver
|
cfg.DeliverSubject = newDeliver
|
||||||
cfg.DeliverPolicy = DeliverByStartSequencePolicy
|
cfg.DeliverPolicy = DeliverByStartSequencePolicy
|
||||||
cfg.OptStartSeq = sseq
|
cfg.OptStartSeq = sseq
|
||||||
|
// In case the consumer was created with a start time, we need to clear it
|
||||||
|
// since we are now using a start sequence.
|
||||||
|
cfg.OptStartTime = nil
|
||||||
|
|
||||||
js := jsi.js
|
js := jsi.js
|
||||||
sub.mu.Unlock()
|
sub.mu.Unlock()
|
||||||
|
|||||||
+16
-7
@@ -361,20 +361,29 @@ func (js *js) upsertConsumer(stream, consumerName string, cfg *ConsumerConfig, o
|
|||||||
|
|
||||||
var ccSubj string
|
var ccSubj string
|
||||||
if consumerName == _EMPTY_ {
|
if consumerName == _EMPTY_ {
|
||||||
// if consumer name is empty, use the legacy ephemeral endpoint
|
// if consumer name is empty (neither Durable nor Name is set), use the legacy ephemeral endpoint
|
||||||
ccSubj = fmt.Sprintf(apiLegacyConsumerCreateT, stream)
|
ccSubj = fmt.Sprintf(apiLegacyConsumerCreateT, stream)
|
||||||
} else if err := checkConsumerName(consumerName); err != nil {
|
} else if err := checkConsumerName(consumerName); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if !js.nc.serverMinVersion(2, 9, 0) || (cfg.Durable != "" && js.opts.featureFlags.useDurableConsumerCreate) {
|
} else if js.nc.serverMinVersion(2, 9, 0) {
|
||||||
// if server version is lower than 2.9.0 or user set the useDurableConsumerCreate flag, use the legacy DURABLE.CREATE endpoint
|
if cfg.Durable != "" && js.opts.featureFlags.useDurableConsumerCreate {
|
||||||
ccSubj = fmt.Sprintf(apiDurableCreateT, stream, consumerName)
|
// if user set the useDurableConsumerCreate flag, use the legacy DURABLE.CREATE endpoint
|
||||||
} else {
|
ccSubj = fmt.Sprintf(apiDurableCreateT, stream, consumerName)
|
||||||
// if above server version 2.9.0, use the endpoints with consumer name
|
} else if cfg.FilterSubject == _EMPTY_ || cfg.FilterSubject == ">" {
|
||||||
if cfg.FilterSubject == _EMPTY_ || cfg.FilterSubject == ">" {
|
// if filter subject is empty or ">", use the endpoint without filter subject
|
||||||
ccSubj = fmt.Sprintf(apiConsumerCreateT, stream, consumerName)
|
ccSubj = fmt.Sprintf(apiConsumerCreateT, stream, consumerName)
|
||||||
} else {
|
} else {
|
||||||
|
// if filter subject is not empty, use the endpoint with filter subject
|
||||||
ccSubj = fmt.Sprintf(apiConsumerCreateWithFilterSubjectT, stream, consumerName, cfg.FilterSubject)
|
ccSubj = fmt.Sprintf(apiConsumerCreateWithFilterSubjectT, stream, consumerName, cfg.FilterSubject)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if cfg.Durable != "" {
|
||||||
|
// if Durable is set, use the DURABLE.CREATE endpoint
|
||||||
|
ccSubj = fmt.Sprintf(apiDurableCreateT, stream, consumerName)
|
||||||
|
} else {
|
||||||
|
// if Durable is not set, use the legacy ephemeral endpoint
|
||||||
|
ccSubj = fmt.Sprintf(apiLegacyConsumerCreateT, stream)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := js.apiRequestWithContext(o.ctx, js.apiSubj(ccSubj), req)
|
resp, err := js.apiRequestWithContext(o.ctx, js.apiSubj(ccSubj), req)
|
||||||
|
|||||||
+83
@@ -0,0 +1,83 @@
|
|||||||
|
# Legacy JetStream API
|
||||||
|
|
||||||
|
This is a documentation for the legacy JetStream API. A README for the current
|
||||||
|
API can be found [here](jetstream/README.md)
|
||||||
|
|
||||||
|
## JetStream Basic Usage
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/nats-io/nats.go"
|
||||||
|
|
||||||
|
// Connect to NATS
|
||||||
|
nc, _ := nats.Connect(nats.DefaultURL)
|
||||||
|
|
||||||
|
// Create JetStream Context
|
||||||
|
js, _ := nc.JetStream(nats.PublishAsyncMaxPending(256))
|
||||||
|
|
||||||
|
// Simple Stream Publisher
|
||||||
|
js.Publish("ORDERS.scratch", []byte("hello"))
|
||||||
|
|
||||||
|
// Simple Async Stream Publisher
|
||||||
|
for i := 0; i < 500; i++ {
|
||||||
|
js.PublishAsync("ORDERS.scratch", []byte("hello"))
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-js.PublishAsyncComplete():
|
||||||
|
case <-time.After(5 * time.Second):
|
||||||
|
fmt.Println("Did not resolve in time")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple Async Ephemeral Consumer
|
||||||
|
js.Subscribe("ORDERS.*", func(m *nats.Msg) {
|
||||||
|
fmt.Printf("Received a JetStream message: %s\n", string(m.Data))
|
||||||
|
})
|
||||||
|
|
||||||
|
// Simple Sync Durable Consumer (optional SubOpts at the end)
|
||||||
|
sub, err := js.SubscribeSync("ORDERS.*", nats.Durable("MONITOR"), nats.MaxDeliver(3))
|
||||||
|
m, err := sub.NextMsg(timeout)
|
||||||
|
|
||||||
|
// Simple Pull Consumer
|
||||||
|
sub, err := js.PullSubscribe("ORDERS.*", "MONITOR")
|
||||||
|
msgs, err := sub.Fetch(10)
|
||||||
|
|
||||||
|
// Unsubscribe
|
||||||
|
sub.Unsubscribe()
|
||||||
|
|
||||||
|
// Drain
|
||||||
|
sub.Drain()
|
||||||
|
```
|
||||||
|
|
||||||
|
## JetStream Basic Management
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/nats-io/nats.go"
|
||||||
|
|
||||||
|
// Connect to NATS
|
||||||
|
nc, _ := nats.Connect(nats.DefaultURL)
|
||||||
|
|
||||||
|
// Create JetStream Context
|
||||||
|
js, _ := nc.JetStream()
|
||||||
|
|
||||||
|
// Create a Stream
|
||||||
|
js.AddStream(&nats.StreamConfig{
|
||||||
|
Name: "ORDERS",
|
||||||
|
Subjects: []string{"ORDERS.*"},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Update a Stream
|
||||||
|
js.UpdateStream(&nats.StreamConfig{
|
||||||
|
Name: "ORDERS",
|
||||||
|
MaxBytes: 8,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create a Consumer
|
||||||
|
js.AddConsumer("ORDERS", &nats.ConsumerConfig{
|
||||||
|
Durable: "MONITOR",
|
||||||
|
})
|
||||||
|
|
||||||
|
// Delete Consumer
|
||||||
|
js.DeleteConsumer("ORDERS", "MONITOR")
|
||||||
|
|
||||||
|
// Delete Stream
|
||||||
|
js.DeleteStream("ORDERS")
|
||||||
|
```
|
||||||
+2
-2
@@ -47,7 +47,7 @@ import (
|
|||||||
|
|
||||||
// Default Constants
|
// Default Constants
|
||||||
const (
|
const (
|
||||||
Version = "1.27.0"
|
Version = "1.28.0"
|
||||||
DefaultURL = "nats://127.0.0.1:4222"
|
DefaultURL = "nats://127.0.0.1:4222"
|
||||||
DefaultPort = 4222
|
DefaultPort = 4222
|
||||||
DefaultMaxReconnect = 60
|
DefaultMaxReconnect = 60
|
||||||
@@ -5471,7 +5471,7 @@ func (nc *Conn) StatusChanged(statuses ...Status) chan Status {
|
|||||||
if len(statuses) == 0 {
|
if len(statuses) == 0 {
|
||||||
statuses = []Status{CONNECTED, RECONNECTING, DISCONNECTED, CLOSED}
|
statuses = []Status{CONNECTED, RECONNECTING, DISCONNECTED, CLOSED}
|
||||||
}
|
}
|
||||||
ch := make(chan Status)
|
ch := make(chan Status, 10)
|
||||||
for _, s := range statuses {
|
for _, s := range statuses {
|
||||||
nc.registerStatusChangeListener(s, ch)
|
nc.registerStatusChangeListener(s, ch)
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -23,7 +23,7 @@ import (
|
|||||||
// Data will be encoded and decoded via the EncodedConn and its associated encoders.
|
// Data will be encoded and decoded via the EncodedConn and its associated encoders.
|
||||||
|
|
||||||
// BindSendChan binds a channel for send operations to NATS.
|
// BindSendChan binds a channel for send operations to NATS.
|
||||||
func (c *EncodedConn) BindSendChan(subject string, channel interface{}) error {
|
func (c *EncodedConn) BindSendChan(subject string, channel any) error {
|
||||||
chVal := reflect.ValueOf(channel)
|
chVal := reflect.ValueOf(channel)
|
||||||
if chVal.Kind() != reflect.Chan {
|
if chVal.Kind() != reflect.Chan {
|
||||||
return ErrChanArg
|
return ErrChanArg
|
||||||
@@ -61,17 +61,17 @@ func chPublish(c *EncodedConn, chVal reflect.Value, subject string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// BindRecvChan binds a channel for receive operations from NATS.
|
// BindRecvChan binds a channel for receive operations from NATS.
|
||||||
func (c *EncodedConn) BindRecvChan(subject string, channel interface{}) (*Subscription, error) {
|
func (c *EncodedConn) BindRecvChan(subject string, channel any) (*Subscription, error) {
|
||||||
return c.bindRecvChan(subject, _EMPTY_, channel)
|
return c.bindRecvChan(subject, _EMPTY_, channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BindRecvQueueChan binds a channel for queue-based receive operations from NATS.
|
// BindRecvQueueChan binds a channel for queue-based receive operations from NATS.
|
||||||
func (c *EncodedConn) BindRecvQueueChan(subject, queue string, channel interface{}) (*Subscription, error) {
|
func (c *EncodedConn) BindRecvQueueChan(subject, queue string, channel any) (*Subscription, error) {
|
||||||
return c.bindRecvChan(subject, queue, channel)
|
return c.bindRecvChan(subject, queue, channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal function to bind receive operations for a channel.
|
// Internal function to bind receive operations for a channel.
|
||||||
func (c *EncodedConn) bindRecvChan(subject, queue string, channel interface{}) (*Subscription, error) {
|
func (c *EncodedConn) bindRecvChan(subject, queue string, channel any) (*Subscription, error) {
|
||||||
chVal := reflect.ValueOf(channel)
|
chVal := reflect.ValueOf(channel)
|
||||||
if chVal.Kind() != reflect.Chan {
|
if chVal.Kind() != reflect.Chan {
|
||||||
return nil, ErrChanArg
|
return nil, ErrChanArg
|
||||||
|
|||||||
+1
@@ -622,6 +622,7 @@ func (obs *obs) Get(name string, opts ...GetObjectOpt) (ObjectResult, error) {
|
|||||||
result.digest = sha256.New()
|
result.digest = sha256.New()
|
||||||
|
|
||||||
processChunk := func(m *Msg) {
|
processChunk := func(m *Msg) {
|
||||||
|
var err error
|
||||||
if ctx != nil {
|
if ctx != nil {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
|||||||
+824
@@ -0,0 +1,824 @@
|
|||||||
|
// Copyright 2017 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package cryptobyte
|
||||||
|
|
||||||
|
import (
|
||||||
|
encoding_asn1 "encoding/asn1"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/cryptobyte/asn1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This file contains ASN.1-related methods for String and Builder.
|
||||||
|
|
||||||
|
// Builder
|
||||||
|
|
||||||
|
// AddASN1Int64 appends a DER-encoded ASN.1 INTEGER.
|
||||||
|
func (b *Builder) AddASN1Int64(v int64) {
|
||||||
|
b.addASN1Signed(asn1.INTEGER, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddASN1Int64WithTag appends a DER-encoded ASN.1 INTEGER with the
|
||||||
|
// given tag.
|
||||||
|
func (b *Builder) AddASN1Int64WithTag(v int64, tag asn1.Tag) {
|
||||||
|
b.addASN1Signed(tag, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddASN1Enum appends a DER-encoded ASN.1 ENUMERATION.
|
||||||
|
func (b *Builder) AddASN1Enum(v int64) {
|
||||||
|
b.addASN1Signed(asn1.ENUM, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) addASN1Signed(tag asn1.Tag, v int64) {
|
||||||
|
b.AddASN1(tag, func(c *Builder) {
|
||||||
|
length := 1
|
||||||
|
for i := v; i >= 0x80 || i < -0x80; i >>= 8 {
|
||||||
|
length++
|
||||||
|
}
|
||||||
|
|
||||||
|
for ; length > 0; length-- {
|
||||||
|
i := v >> uint((length-1)*8) & 0xff
|
||||||
|
c.AddUint8(uint8(i))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddASN1Uint64 appends a DER-encoded ASN.1 INTEGER.
|
||||||
|
func (b *Builder) AddASN1Uint64(v uint64) {
|
||||||
|
b.AddASN1(asn1.INTEGER, func(c *Builder) {
|
||||||
|
length := 1
|
||||||
|
for i := v; i >= 0x80; i >>= 8 {
|
||||||
|
length++
|
||||||
|
}
|
||||||
|
|
||||||
|
for ; length > 0; length-- {
|
||||||
|
i := v >> uint((length-1)*8) & 0xff
|
||||||
|
c.AddUint8(uint8(i))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddASN1BigInt appends a DER-encoded ASN.1 INTEGER.
|
||||||
|
func (b *Builder) AddASN1BigInt(n *big.Int) {
|
||||||
|
if b.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b.AddASN1(asn1.INTEGER, func(c *Builder) {
|
||||||
|
if n.Sign() < 0 {
|
||||||
|
// A negative number has to be converted to two's-complement form. So we
|
||||||
|
// invert and subtract 1. If the most-significant-bit isn't set then
|
||||||
|
// we'll need to pad the beginning with 0xff in order to keep the number
|
||||||
|
// negative.
|
||||||
|
nMinus1 := new(big.Int).Neg(n)
|
||||||
|
nMinus1.Sub(nMinus1, bigOne)
|
||||||
|
bytes := nMinus1.Bytes()
|
||||||
|
for i := range bytes {
|
||||||
|
bytes[i] ^= 0xff
|
||||||
|
}
|
||||||
|
if len(bytes) == 0 || bytes[0]&0x80 == 0 {
|
||||||
|
c.add(0xff)
|
||||||
|
}
|
||||||
|
c.add(bytes...)
|
||||||
|
} else if n.Sign() == 0 {
|
||||||
|
c.add(0)
|
||||||
|
} else {
|
||||||
|
bytes := n.Bytes()
|
||||||
|
if bytes[0]&0x80 != 0 {
|
||||||
|
c.add(0)
|
||||||
|
}
|
||||||
|
c.add(bytes...)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddASN1OctetString appends a DER-encoded ASN.1 OCTET STRING.
|
||||||
|
func (b *Builder) AddASN1OctetString(bytes []byte) {
|
||||||
|
b.AddASN1(asn1.OCTET_STRING, func(c *Builder) {
|
||||||
|
c.AddBytes(bytes)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const generalizedTimeFormatStr = "20060102150405Z0700"
|
||||||
|
|
||||||
|
// AddASN1GeneralizedTime appends a DER-encoded ASN.1 GENERALIZEDTIME.
|
||||||
|
func (b *Builder) AddASN1GeneralizedTime(t time.Time) {
|
||||||
|
if t.Year() < 0 || t.Year() > 9999 {
|
||||||
|
b.err = fmt.Errorf("cryptobyte: cannot represent %v as a GeneralizedTime", t)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.AddASN1(asn1.GeneralizedTime, func(c *Builder) {
|
||||||
|
c.AddBytes([]byte(t.Format(generalizedTimeFormatStr)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddASN1UTCTime appends a DER-encoded ASN.1 UTCTime.
|
||||||
|
func (b *Builder) AddASN1UTCTime(t time.Time) {
|
||||||
|
b.AddASN1(asn1.UTCTime, func(c *Builder) {
|
||||||
|
// As utilized by the X.509 profile, UTCTime can only
|
||||||
|
// represent the years 1950 through 2049.
|
||||||
|
if t.Year() < 1950 || t.Year() >= 2050 {
|
||||||
|
b.err = fmt.Errorf("cryptobyte: cannot represent %v as a UTCTime", t)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.AddBytes([]byte(t.Format(defaultUTCTimeFormatStr)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddASN1BitString appends a DER-encoded ASN.1 BIT STRING. This does not
|
||||||
|
// support BIT STRINGs that are not a whole number of bytes.
|
||||||
|
func (b *Builder) AddASN1BitString(data []byte) {
|
||||||
|
b.AddASN1(asn1.BIT_STRING, func(b *Builder) {
|
||||||
|
b.AddUint8(0)
|
||||||
|
b.AddBytes(data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) addBase128Int(n int64) {
|
||||||
|
var length int
|
||||||
|
if n == 0 {
|
||||||
|
length = 1
|
||||||
|
} else {
|
||||||
|
for i := n; i > 0; i >>= 7 {
|
||||||
|
length++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := length - 1; i >= 0; i-- {
|
||||||
|
o := byte(n >> uint(i*7))
|
||||||
|
o &= 0x7f
|
||||||
|
if i != 0 {
|
||||||
|
o |= 0x80
|
||||||
|
}
|
||||||
|
|
||||||
|
b.add(o)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValidOID(oid encoding_asn1.ObjectIdentifier) bool {
|
||||||
|
if len(oid) < 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if oid[0] > 2 || (oid[0] <= 1 && oid[1] >= 40) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range oid {
|
||||||
|
if v < 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) AddASN1ObjectIdentifier(oid encoding_asn1.ObjectIdentifier) {
|
||||||
|
b.AddASN1(asn1.OBJECT_IDENTIFIER, func(b *Builder) {
|
||||||
|
if !isValidOID(oid) {
|
||||||
|
b.err = fmt.Errorf("cryptobyte: invalid OID: %v", oid)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b.addBase128Int(int64(oid[0])*40 + int64(oid[1]))
|
||||||
|
for _, v := range oid[2:] {
|
||||||
|
b.addBase128Int(int64(v))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) AddASN1Boolean(v bool) {
|
||||||
|
b.AddASN1(asn1.BOOLEAN, func(b *Builder) {
|
||||||
|
if v {
|
||||||
|
b.AddUint8(0xff)
|
||||||
|
} else {
|
||||||
|
b.AddUint8(0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) AddASN1NULL() {
|
||||||
|
b.add(uint8(asn1.NULL), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalASN1 calls encoding_asn1.Marshal on its input and appends the result if
|
||||||
|
// successful or records an error if one occurred.
|
||||||
|
func (b *Builder) MarshalASN1(v interface{}) {
|
||||||
|
// NOTE(martinkr): This is somewhat of a hack to allow propagation of
|
||||||
|
// encoding_asn1.Marshal errors into Builder.err. N.B. if you call MarshalASN1 with a
|
||||||
|
// value embedded into a struct, its tag information is lost.
|
||||||
|
if b.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bytes, err := encoding_asn1.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
b.err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.AddBytes(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddASN1 appends an ASN.1 object. The object is prefixed with the given tag.
|
||||||
|
// Tags greater than 30 are not supported and result in an error (i.e.
|
||||||
|
// low-tag-number form only). The child builder passed to the
|
||||||
|
// BuilderContinuation can be used to build the content of the ASN.1 object.
|
||||||
|
func (b *Builder) AddASN1(tag asn1.Tag, f BuilderContinuation) {
|
||||||
|
if b.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Identifiers with the low five bits set indicate high-tag-number format
|
||||||
|
// (two or more octets), which we don't support.
|
||||||
|
if tag&0x1f == 0x1f {
|
||||||
|
b.err = fmt.Errorf("cryptobyte: high-tag number identifier octects not supported: 0x%x", tag)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.AddUint8(uint8(tag))
|
||||||
|
b.addLengthPrefixed(1, true, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String
|
||||||
|
|
||||||
|
// ReadASN1Boolean decodes an ASN.1 BOOLEAN and converts it to a boolean
|
||||||
|
// representation into out and advances. It reports whether the read
|
||||||
|
// was successful.
|
||||||
|
func (s *String) ReadASN1Boolean(out *bool) bool {
|
||||||
|
var bytes String
|
||||||
|
if !s.ReadASN1(&bytes, asn1.BOOLEAN) || len(bytes) != 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch bytes[0] {
|
||||||
|
case 0:
|
||||||
|
*out = false
|
||||||
|
case 0xff:
|
||||||
|
*out = true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadASN1Integer decodes an ASN.1 INTEGER into out and advances. If out does
|
||||||
|
// not point to an integer, to a big.Int, or to a []byte it panics. Only
|
||||||
|
// positive and zero values can be decoded into []byte, and they are returned as
|
||||||
|
// big-endian binary values that share memory with s. Positive values will have
|
||||||
|
// no leading zeroes, and zero will be returned as a single zero byte.
|
||||||
|
// ReadASN1Integer reports whether the read was successful.
|
||||||
|
func (s *String) ReadASN1Integer(out interface{}) bool {
|
||||||
|
switch out := out.(type) {
|
||||||
|
case *int, *int8, *int16, *int32, *int64:
|
||||||
|
var i int64
|
||||||
|
if !s.readASN1Int64(&i) || reflect.ValueOf(out).Elem().OverflowInt(i) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
reflect.ValueOf(out).Elem().SetInt(i)
|
||||||
|
return true
|
||||||
|
case *uint, *uint8, *uint16, *uint32, *uint64:
|
||||||
|
var u uint64
|
||||||
|
if !s.readASN1Uint64(&u) || reflect.ValueOf(out).Elem().OverflowUint(u) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
reflect.ValueOf(out).Elem().SetUint(u)
|
||||||
|
return true
|
||||||
|
case *big.Int:
|
||||||
|
return s.readASN1BigInt(out)
|
||||||
|
case *[]byte:
|
||||||
|
return s.readASN1Bytes(out)
|
||||||
|
default:
|
||||||
|
panic("out does not point to an integer type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkASN1Integer(bytes []byte) bool {
|
||||||
|
if len(bytes) == 0 {
|
||||||
|
// An INTEGER is encoded with at least one octet.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(bytes) == 1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if bytes[0] == 0 && bytes[1]&0x80 == 0 || bytes[0] == 0xff && bytes[1]&0x80 == 0x80 {
|
||||||
|
// Value is not minimally encoded.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var bigOne = big.NewInt(1)
|
||||||
|
|
||||||
|
func (s *String) readASN1BigInt(out *big.Int) bool {
|
||||||
|
var bytes String
|
||||||
|
if !s.ReadASN1(&bytes, asn1.INTEGER) || !checkASN1Integer(bytes) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if bytes[0]&0x80 == 0x80 {
|
||||||
|
// Negative number.
|
||||||
|
neg := make([]byte, len(bytes))
|
||||||
|
for i, b := range bytes {
|
||||||
|
neg[i] = ^b
|
||||||
|
}
|
||||||
|
out.SetBytes(neg)
|
||||||
|
out.Add(out, bigOne)
|
||||||
|
out.Neg(out)
|
||||||
|
} else {
|
||||||
|
out.SetBytes(bytes)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *String) readASN1Bytes(out *[]byte) bool {
|
||||||
|
var bytes String
|
||||||
|
if !s.ReadASN1(&bytes, asn1.INTEGER) || !checkASN1Integer(bytes) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if bytes[0]&0x80 == 0x80 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for len(bytes) > 1 && bytes[0] == 0 {
|
||||||
|
bytes = bytes[1:]
|
||||||
|
}
|
||||||
|
*out = bytes
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *String) readASN1Int64(out *int64) bool {
|
||||||
|
var bytes String
|
||||||
|
if !s.ReadASN1(&bytes, asn1.INTEGER) || !checkASN1Integer(bytes) || !asn1Signed(out, bytes) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func asn1Signed(out *int64, n []byte) bool {
|
||||||
|
length := len(n)
|
||||||
|
if length > 8 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
*out <<= 8
|
||||||
|
*out |= int64(n[i])
|
||||||
|
}
|
||||||
|
// Shift up and down in order to sign extend the result.
|
||||||
|
*out <<= 64 - uint8(length)*8
|
||||||
|
*out >>= 64 - uint8(length)*8
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *String) readASN1Uint64(out *uint64) bool {
|
||||||
|
var bytes String
|
||||||
|
if !s.ReadASN1(&bytes, asn1.INTEGER) || !checkASN1Integer(bytes) || !asn1Unsigned(out, bytes) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func asn1Unsigned(out *uint64, n []byte) bool {
|
||||||
|
length := len(n)
|
||||||
|
if length > 9 || length == 9 && n[0] != 0 {
|
||||||
|
// Too large for uint64.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if n[0]&0x80 != 0 {
|
||||||
|
// Negative number.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
*out <<= 8
|
||||||
|
*out |= uint64(n[i])
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadASN1Int64WithTag decodes an ASN.1 INTEGER with the given tag into out
|
||||||
|
// and advances. It reports whether the read was successful and resulted in a
|
||||||
|
// value that can be represented in an int64.
|
||||||
|
func (s *String) ReadASN1Int64WithTag(out *int64, tag asn1.Tag) bool {
|
||||||
|
var bytes String
|
||||||
|
return s.ReadASN1(&bytes, tag) && checkASN1Integer(bytes) && asn1Signed(out, bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadASN1Enum decodes an ASN.1 ENUMERATION into out and advances. It reports
|
||||||
|
// whether the read was successful.
|
||||||
|
func (s *String) ReadASN1Enum(out *int) bool {
|
||||||
|
var bytes String
|
||||||
|
var i int64
|
||||||
|
if !s.ReadASN1(&bytes, asn1.ENUM) || !checkASN1Integer(bytes) || !asn1Signed(&i, bytes) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if int64(int(i)) != i {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
*out = int(i)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *String) readBase128Int(out *int) bool {
|
||||||
|
ret := 0
|
||||||
|
for i := 0; len(*s) > 0; i++ {
|
||||||
|
if i == 5 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Avoid overflowing int on a 32-bit platform.
|
||||||
|
// We don't want different behavior based on the architecture.
|
||||||
|
if ret >= 1<<(31-7) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
ret <<= 7
|
||||||
|
b := s.read(1)[0]
|
||||||
|
|
||||||
|
// ITU-T X.690, section 8.19.2:
|
||||||
|
// The subidentifier shall be encoded in the fewest possible octets,
|
||||||
|
// that is, the leading octet of the subidentifier shall not have the value 0x80.
|
||||||
|
if i == 0 && b == 0x80 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
ret |= int(b & 0x7f)
|
||||||
|
if b&0x80 == 0 {
|
||||||
|
*out = ret
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false // truncated
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadASN1ObjectIdentifier decodes an ASN.1 OBJECT IDENTIFIER into out and
|
||||||
|
// advances. It reports whether the read was successful.
|
||||||
|
func (s *String) ReadASN1ObjectIdentifier(out *encoding_asn1.ObjectIdentifier) bool {
|
||||||
|
var bytes String
|
||||||
|
if !s.ReadASN1(&bytes, asn1.OBJECT_IDENTIFIER) || len(bytes) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// In the worst case, we get two elements from the first byte (which is
|
||||||
|
// encoded differently) and then every varint is a single byte long.
|
||||||
|
components := make([]int, len(bytes)+1)
|
||||||
|
|
||||||
|
// The first varint is 40*value1 + value2:
|
||||||
|
// According to this packing, value1 can take the values 0, 1 and 2 only.
|
||||||
|
// When value1 = 0 or value1 = 1, then value2 is <= 39. When value1 = 2,
|
||||||
|
// then there are no restrictions on value2.
|
||||||
|
var v int
|
||||||
|
if !bytes.readBase128Int(&v) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if v < 80 {
|
||||||
|
components[0] = v / 40
|
||||||
|
components[1] = v % 40
|
||||||
|
} else {
|
||||||
|
components[0] = 2
|
||||||
|
components[1] = v - 80
|
||||||
|
}
|
||||||
|
|
||||||
|
i := 2
|
||||||
|
for ; len(bytes) > 0; i++ {
|
||||||
|
if !bytes.readBase128Int(&v) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
components[i] = v
|
||||||
|
}
|
||||||
|
*out = components[:i]
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadASN1GeneralizedTime decodes an ASN.1 GENERALIZEDTIME into out and
|
||||||
|
// advances. It reports whether the read was successful.
|
||||||
|
func (s *String) ReadASN1GeneralizedTime(out *time.Time) bool {
|
||||||
|
var bytes String
|
||||||
|
if !s.ReadASN1(&bytes, asn1.GeneralizedTime) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
t := string(bytes)
|
||||||
|
res, err := time.Parse(generalizedTimeFormatStr, t)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if serialized := res.Format(generalizedTimeFormatStr); serialized != t {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
*out = res
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultUTCTimeFormatStr = "060102150405Z0700"
|
||||||
|
|
||||||
|
// ReadASN1UTCTime decodes an ASN.1 UTCTime into out and advances.
|
||||||
|
// It reports whether the read was successful.
|
||||||
|
func (s *String) ReadASN1UTCTime(out *time.Time) bool {
|
||||||
|
var bytes String
|
||||||
|
if !s.ReadASN1(&bytes, asn1.UTCTime) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
t := string(bytes)
|
||||||
|
|
||||||
|
formatStr := defaultUTCTimeFormatStr
|
||||||
|
var err error
|
||||||
|
res, err := time.Parse(formatStr, t)
|
||||||
|
if err != nil {
|
||||||
|
// Fallback to minute precision if we can't parse second
|
||||||
|
// precision. If we are following X.509 or X.690 we shouldn't
|
||||||
|
// support this, but we do.
|
||||||
|
formatStr = "0601021504Z0700"
|
||||||
|
res, err = time.Parse(formatStr, t)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if serialized := res.Format(formatStr); serialized != t {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.Year() >= 2050 {
|
||||||
|
// UTCTime interprets the low order digits 50-99 as 1950-99.
|
||||||
|
// This only applies to its use in the X.509 profile.
|
||||||
|
// See https://tools.ietf.org/html/rfc5280#section-4.1.2.5.1
|
||||||
|
res = res.AddDate(-100, 0, 0)
|
||||||
|
}
|
||||||
|
*out = res
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadASN1BitString decodes an ASN.1 BIT STRING into out and advances.
|
||||||
|
// It reports whether the read was successful.
|
||||||
|
func (s *String) ReadASN1BitString(out *encoding_asn1.BitString) bool {
|
||||||
|
var bytes String
|
||||||
|
if !s.ReadASN1(&bytes, asn1.BIT_STRING) || len(bytes) == 0 ||
|
||||||
|
len(bytes)*8/8 != len(bytes) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
paddingBits := bytes[0]
|
||||||
|
bytes = bytes[1:]
|
||||||
|
if paddingBits > 7 ||
|
||||||
|
len(bytes) == 0 && paddingBits != 0 ||
|
||||||
|
len(bytes) > 0 && bytes[len(bytes)-1]&(1<<paddingBits-1) != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
out.BitLength = len(bytes)*8 - int(paddingBits)
|
||||||
|
out.Bytes = bytes
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadASN1BitStringAsBytes decodes an ASN.1 BIT STRING into out and advances. It is
|
||||||
|
// an error if the BIT STRING is not a whole number of bytes. It reports
|
||||||
|
// whether the read was successful.
|
||||||
|
func (s *String) ReadASN1BitStringAsBytes(out *[]byte) bool {
|
||||||
|
var bytes String
|
||||||
|
if !s.ReadASN1(&bytes, asn1.BIT_STRING) || len(bytes) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
paddingBits := bytes[0]
|
||||||
|
if paddingBits != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
*out = bytes[1:]
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadASN1Bytes reads the contents of a DER-encoded ASN.1 element (not including
|
||||||
|
// tag and length bytes) into out, and advances. The element must match the
|
||||||
|
// given tag. It reports whether the read was successful.
|
||||||
|
func (s *String) ReadASN1Bytes(out *[]byte, tag asn1.Tag) bool {
|
||||||
|
return s.ReadASN1((*String)(out), tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadASN1 reads the contents of a DER-encoded ASN.1 element (not including
|
||||||
|
// tag and length bytes) into out, and advances. The element must match the
|
||||||
|
// given tag. It reports whether the read was successful.
|
||||||
|
//
|
||||||
|
// Tags greater than 30 are not supported (i.e. low-tag-number format only).
|
||||||
|
func (s *String) ReadASN1(out *String, tag asn1.Tag) bool {
|
||||||
|
var t asn1.Tag
|
||||||
|
if !s.ReadAnyASN1(out, &t) || t != tag {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadASN1Element reads the contents of a DER-encoded ASN.1 element (including
|
||||||
|
// tag and length bytes) into out, and advances. The element must match the
|
||||||
|
// given tag. It reports whether the read was successful.
|
||||||
|
//
|
||||||
|
// Tags greater than 30 are not supported (i.e. low-tag-number format only).
|
||||||
|
func (s *String) ReadASN1Element(out *String, tag asn1.Tag) bool {
|
||||||
|
var t asn1.Tag
|
||||||
|
if !s.ReadAnyASN1Element(out, &t) || t != tag {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadAnyASN1 reads the contents of a DER-encoded ASN.1 element (not including
|
||||||
|
// tag and length bytes) into out, sets outTag to its tag, and advances.
|
||||||
|
// It reports whether the read was successful.
|
||||||
|
//
|
||||||
|
// Tags greater than 30 are not supported (i.e. low-tag-number format only).
|
||||||
|
func (s *String) ReadAnyASN1(out *String, outTag *asn1.Tag) bool {
|
||||||
|
return s.readASN1(out, outTag, true /* skip header */)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadAnyASN1Element reads the contents of a DER-encoded ASN.1 element
|
||||||
|
// (including tag and length bytes) into out, sets outTag to is tag, and
|
||||||
|
// advances. It reports whether the read was successful.
|
||||||
|
//
|
||||||
|
// Tags greater than 30 are not supported (i.e. low-tag-number format only).
|
||||||
|
func (s *String) ReadAnyASN1Element(out *String, outTag *asn1.Tag) bool {
|
||||||
|
return s.readASN1(out, outTag, false /* include header */)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PeekASN1Tag reports whether the next ASN.1 value on the string starts with
|
||||||
|
// the given tag.
|
||||||
|
func (s String) PeekASN1Tag(tag asn1.Tag) bool {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return asn1.Tag(s[0]) == tag
|
||||||
|
}
|
||||||
|
|
||||||
|
// SkipASN1 reads and discards an ASN.1 element with the given tag. It
|
||||||
|
// reports whether the operation was successful.
|
||||||
|
func (s *String) SkipASN1(tag asn1.Tag) bool {
|
||||||
|
var unused String
|
||||||
|
return s.ReadASN1(&unused, tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadOptionalASN1 attempts to read the contents of a DER-encoded ASN.1
|
||||||
|
// element (not including tag and length bytes) tagged with the given tag into
|
||||||
|
// out. It stores whether an element with the tag was found in outPresent,
|
||||||
|
// unless outPresent is nil. It reports whether the read was successful.
|
||||||
|
func (s *String) ReadOptionalASN1(out *String, outPresent *bool, tag asn1.Tag) bool {
|
||||||
|
present := s.PeekASN1Tag(tag)
|
||||||
|
if outPresent != nil {
|
||||||
|
*outPresent = present
|
||||||
|
}
|
||||||
|
if present && !s.ReadASN1(out, tag) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SkipOptionalASN1 advances s over an ASN.1 element with the given tag, or
|
||||||
|
// else leaves s unchanged. It reports whether the operation was successful.
|
||||||
|
func (s *String) SkipOptionalASN1(tag asn1.Tag) bool {
|
||||||
|
if !s.PeekASN1Tag(tag) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
var unused String
|
||||||
|
return s.ReadASN1(&unused, tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadOptionalASN1Integer attempts to read an optional ASN.1 INTEGER explicitly
|
||||||
|
// tagged with tag into out and advances. If no element with a matching tag is
|
||||||
|
// present, it writes defaultValue into out instead. Otherwise, it behaves like
|
||||||
|
// ReadASN1Integer.
|
||||||
|
func (s *String) ReadOptionalASN1Integer(out interface{}, tag asn1.Tag, defaultValue interface{}) bool {
|
||||||
|
var present bool
|
||||||
|
var i String
|
||||||
|
if !s.ReadOptionalASN1(&i, &present, tag) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !present {
|
||||||
|
switch out.(type) {
|
||||||
|
case *int, *int8, *int16, *int32, *int64,
|
||||||
|
*uint, *uint8, *uint16, *uint32, *uint64, *[]byte:
|
||||||
|
reflect.ValueOf(out).Elem().Set(reflect.ValueOf(defaultValue))
|
||||||
|
case *big.Int:
|
||||||
|
if defaultValue, ok := defaultValue.(*big.Int); ok {
|
||||||
|
out.(*big.Int).Set(defaultValue)
|
||||||
|
} else {
|
||||||
|
panic("out points to big.Int, but defaultValue does not")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("invalid integer type")
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if !i.ReadASN1Integer(out) || !i.Empty() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadOptionalASN1OctetString attempts to read an optional ASN.1 OCTET STRING
|
||||||
|
// explicitly tagged with tag into out and advances. If no element with a
|
||||||
|
// matching tag is present, it sets "out" to nil instead. It reports
|
||||||
|
// whether the read was successful.
|
||||||
|
func (s *String) ReadOptionalASN1OctetString(out *[]byte, outPresent *bool, tag asn1.Tag) bool {
|
||||||
|
var present bool
|
||||||
|
var child String
|
||||||
|
if !s.ReadOptionalASN1(&child, &present, tag) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if outPresent != nil {
|
||||||
|
*outPresent = present
|
||||||
|
}
|
||||||
|
if present {
|
||||||
|
var oct String
|
||||||
|
if !child.ReadASN1(&oct, asn1.OCTET_STRING) || !child.Empty() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
*out = oct
|
||||||
|
} else {
|
||||||
|
*out = nil
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadOptionalASN1Boolean sets *out to the value of the next ASN.1 BOOLEAN or,
|
||||||
|
// if the next bytes are not an ASN.1 BOOLEAN, to the value of defaultValue.
|
||||||
|
// It reports whether the operation was successful.
|
||||||
|
func (s *String) ReadOptionalASN1Boolean(out *bool, defaultValue bool) bool {
|
||||||
|
var present bool
|
||||||
|
var child String
|
||||||
|
if !s.ReadOptionalASN1(&child, &present, asn1.BOOLEAN) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !present {
|
||||||
|
*out = defaultValue
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.ReadASN1Boolean(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *String) readASN1(out *String, outTag *asn1.Tag, skipHeader bool) bool {
|
||||||
|
if len(*s) < 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
tag, lenByte := (*s)[0], (*s)[1]
|
||||||
|
|
||||||
|
if tag&0x1f == 0x1f {
|
||||||
|
// ITU-T X.690 section 8.1.2
|
||||||
|
//
|
||||||
|
// An identifier octet with a tag part of 0x1f indicates a high-tag-number
|
||||||
|
// form identifier with two or more octets. We only support tags less than
|
||||||
|
// 31 (i.e. low-tag-number form, single octet identifier).
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if outTag != nil {
|
||||||
|
*outTag = asn1.Tag(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ITU-T X.690 section 8.1.3
|
||||||
|
//
|
||||||
|
// Bit 8 of the first length byte indicates whether the length is short- or
|
||||||
|
// long-form.
|
||||||
|
var length, headerLen uint32 // length includes headerLen
|
||||||
|
if lenByte&0x80 == 0 {
|
||||||
|
// Short-form length (section 8.1.3.4), encoded in bits 1-7.
|
||||||
|
length = uint32(lenByte) + 2
|
||||||
|
headerLen = 2
|
||||||
|
} else {
|
||||||
|
// Long-form length (section 8.1.3.5). Bits 1-7 encode the number of octets
|
||||||
|
// used to encode the length.
|
||||||
|
lenLen := lenByte & 0x7f
|
||||||
|
var len32 uint32
|
||||||
|
|
||||||
|
if lenLen == 0 || lenLen > 4 || len(*s) < int(2+lenLen) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
lenBytes := String((*s)[2 : 2+lenLen])
|
||||||
|
if !lenBytes.readUnsigned(&len32, int(lenLen)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ITU-T X.690 section 10.1 (DER length forms) requires encoding the length
|
||||||
|
// with the minimum number of octets.
|
||||||
|
if len32 < 128 {
|
||||||
|
// Length should have used short-form encoding.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len32>>((lenLen-1)*8) == 0 {
|
||||||
|
// Leading octet is 0. Length should have been at least one byte shorter.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
headerLen = 2 + uint32(lenLen)
|
||||||
|
if headerLen+len32 < len32 {
|
||||||
|
// Overflow.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
length = headerLen + len32
|
||||||
|
}
|
||||||
|
|
||||||
|
if int(length) < 0 || !s.ReadBytes((*[]byte)(out), int(length)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if skipHeader && !out.Skip(int(headerLen)) {
|
||||||
|
panic("cryptobyte: internal error")
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
// Copyright 2017 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package asn1 contains supporting types for parsing and building ASN.1
|
||||||
|
// messages with the cryptobyte package.
|
||||||
|
package asn1 // import "golang.org/x/crypto/cryptobyte/asn1"
|
||||||
|
|
||||||
|
// Tag represents an ASN.1 identifier octet, consisting of a tag number
|
||||||
|
// (indicating a type) and class (such as context-specific or constructed).
|
||||||
|
//
|
||||||
|
// Methods in the cryptobyte package only support the low-tag-number form, i.e.
|
||||||
|
// a single identifier octet with bits 7-8 encoding the class and bits 1-6
|
||||||
|
// encoding the tag number.
|
||||||
|
type Tag uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
classConstructed = 0x20
|
||||||
|
classContextSpecific = 0x80
|
||||||
|
)
|
||||||
|
|
||||||
|
// Constructed returns t with the constructed class bit set.
|
||||||
|
func (t Tag) Constructed() Tag { return t | classConstructed }
|
||||||
|
|
||||||
|
// ContextSpecific returns t with the context-specific class bit set.
|
||||||
|
func (t Tag) ContextSpecific() Tag { return t | classContextSpecific }
|
||||||
|
|
||||||
|
// The following is a list of standard tag and class combinations.
|
||||||
|
const (
|
||||||
|
BOOLEAN = Tag(1)
|
||||||
|
INTEGER = Tag(2)
|
||||||
|
BIT_STRING = Tag(3)
|
||||||
|
OCTET_STRING = Tag(4)
|
||||||
|
NULL = Tag(5)
|
||||||
|
OBJECT_IDENTIFIER = Tag(6)
|
||||||
|
ENUM = Tag(10)
|
||||||
|
UTF8String = Tag(12)
|
||||||
|
SEQUENCE = Tag(16 | classConstructed)
|
||||||
|
SET = Tag(17 | classConstructed)
|
||||||
|
PrintableString = Tag(19)
|
||||||
|
T61String = Tag(20)
|
||||||
|
IA5String = Tag(22)
|
||||||
|
UTCTime = Tag(23)
|
||||||
|
GeneralizedTime = Tag(24)
|
||||||
|
GeneralString = Tag(27)
|
||||||
|
)
|
||||||
+345
@@ -0,0 +1,345 @@
|
|||||||
|
// Copyright 2017 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package cryptobyte
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Builder builds byte strings from fixed-length and length-prefixed values.
|
||||||
|
// Builders either allocate space as needed, or are ‘fixed’, which means that
|
||||||
|
// they write into a given buffer and produce an error if it's exhausted.
|
||||||
|
//
|
||||||
|
// The zero value is a usable Builder that allocates space as needed.
|
||||||
|
//
|
||||||
|
// Simple values are marshaled and appended to a Builder using methods on the
|
||||||
|
// Builder. Length-prefixed values are marshaled by providing a
|
||||||
|
// BuilderContinuation, which is a function that writes the inner contents of
|
||||||
|
// the value to a given Builder. See the documentation for BuilderContinuation
|
||||||
|
// for details.
|
||||||
|
type Builder struct {
|
||||||
|
err error
|
||||||
|
result []byte
|
||||||
|
fixedSize bool
|
||||||
|
child *Builder
|
||||||
|
offset int
|
||||||
|
pendingLenLen int
|
||||||
|
pendingIsASN1 bool
|
||||||
|
inContinuation *bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBuilder creates a Builder that appends its output to the given buffer.
|
||||||
|
// Like append(), the slice will be reallocated if its capacity is exceeded.
|
||||||
|
// Use Bytes to get the final buffer.
|
||||||
|
func NewBuilder(buffer []byte) *Builder {
|
||||||
|
return &Builder{
|
||||||
|
result: buffer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFixedBuilder creates a Builder that appends its output into the given
|
||||||
|
// buffer. This builder does not reallocate the output buffer. Writes that
|
||||||
|
// would exceed the buffer's capacity are treated as an error.
|
||||||
|
func NewFixedBuilder(buffer []byte) *Builder {
|
||||||
|
return &Builder{
|
||||||
|
result: buffer,
|
||||||
|
fixedSize: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetError sets the value to be returned as the error from Bytes. Writes
|
||||||
|
// performed after calling SetError are ignored.
|
||||||
|
func (b *Builder) SetError(err error) {
|
||||||
|
b.err = err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns the bytes written by the builder or an error if one has
|
||||||
|
// occurred during building.
|
||||||
|
func (b *Builder) Bytes() ([]byte, error) {
|
||||||
|
if b.err != nil {
|
||||||
|
return nil, b.err
|
||||||
|
}
|
||||||
|
return b.result[b.offset:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BytesOrPanic returns the bytes written by the builder or panics if an error
|
||||||
|
// has occurred during building.
|
||||||
|
func (b *Builder) BytesOrPanic() []byte {
|
||||||
|
if b.err != nil {
|
||||||
|
panic(b.err)
|
||||||
|
}
|
||||||
|
return b.result[b.offset:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddUint8 appends an 8-bit value to the byte string.
|
||||||
|
func (b *Builder) AddUint8(v uint8) {
|
||||||
|
b.add(byte(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddUint16 appends a big-endian, 16-bit value to the byte string.
|
||||||
|
func (b *Builder) AddUint16(v uint16) {
|
||||||
|
b.add(byte(v>>8), byte(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddUint24 appends a big-endian, 24-bit value to the byte string. The highest
|
||||||
|
// byte of the 32-bit input value is silently truncated.
|
||||||
|
func (b *Builder) AddUint24(v uint32) {
|
||||||
|
b.add(byte(v>>16), byte(v>>8), byte(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddUint32 appends a big-endian, 32-bit value to the byte string.
|
||||||
|
func (b *Builder) AddUint32(v uint32) {
|
||||||
|
b.add(byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddUint64 appends a big-endian, 64-bit value to the byte string.
|
||||||
|
func (b *Builder) AddUint64(v uint64) {
|
||||||
|
b.add(byte(v>>56), byte(v>>48), byte(v>>40), byte(v>>32), byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddBytes appends a sequence of bytes to the byte string.
|
||||||
|
func (b *Builder) AddBytes(v []byte) {
|
||||||
|
b.add(v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuilderContinuation is a continuation-passing interface for building
|
||||||
|
// length-prefixed byte sequences. Builder methods for length-prefixed
|
||||||
|
// sequences (AddUint8LengthPrefixed etc) will invoke the BuilderContinuation
|
||||||
|
// supplied to them. The child builder passed to the continuation can be used
|
||||||
|
// to build the content of the length-prefixed sequence. For example:
|
||||||
|
//
|
||||||
|
// parent := cryptobyte.NewBuilder()
|
||||||
|
// parent.AddUint8LengthPrefixed(func (child *Builder) {
|
||||||
|
// child.AddUint8(42)
|
||||||
|
// child.AddUint8LengthPrefixed(func (grandchild *Builder) {
|
||||||
|
// grandchild.AddUint8(5)
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
// It is an error to write more bytes to the child than allowed by the reserved
|
||||||
|
// length prefix. After the continuation returns, the child must be considered
|
||||||
|
// invalid, i.e. users must not store any copies or references of the child
|
||||||
|
// that outlive the continuation.
|
||||||
|
//
|
||||||
|
// If the continuation panics with a value of type BuildError then the inner
|
||||||
|
// error will be returned as the error from Bytes. If the child panics
|
||||||
|
// otherwise then Bytes will repanic with the same value.
|
||||||
|
type BuilderContinuation func(child *Builder)
|
||||||
|
|
||||||
|
// BuildError wraps an error. If a BuilderContinuation panics with this value,
|
||||||
|
// the panic will be recovered and the inner error will be returned from
|
||||||
|
// Builder.Bytes.
|
||||||
|
type BuildError struct {
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddUint8LengthPrefixed adds a 8-bit length-prefixed byte sequence.
|
||||||
|
func (b *Builder) AddUint8LengthPrefixed(f BuilderContinuation) {
|
||||||
|
b.addLengthPrefixed(1, false, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddUint16LengthPrefixed adds a big-endian, 16-bit length-prefixed byte sequence.
|
||||||
|
func (b *Builder) AddUint16LengthPrefixed(f BuilderContinuation) {
|
||||||
|
b.addLengthPrefixed(2, false, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddUint24LengthPrefixed adds a big-endian, 24-bit length-prefixed byte sequence.
|
||||||
|
func (b *Builder) AddUint24LengthPrefixed(f BuilderContinuation) {
|
||||||
|
b.addLengthPrefixed(3, false, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddUint32LengthPrefixed adds a big-endian, 32-bit length-prefixed byte sequence.
|
||||||
|
func (b *Builder) AddUint32LengthPrefixed(f BuilderContinuation) {
|
||||||
|
b.addLengthPrefixed(4, false, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) callContinuation(f BuilderContinuation, arg *Builder) {
|
||||||
|
if !*b.inContinuation {
|
||||||
|
*b.inContinuation = true
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
*b.inContinuation = false
|
||||||
|
|
||||||
|
r := recover()
|
||||||
|
if r == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if buildError, ok := r.(BuildError); ok {
|
||||||
|
b.err = buildError.Err
|
||||||
|
} else {
|
||||||
|
panic(r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
f(arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) addLengthPrefixed(lenLen int, isASN1 bool, f BuilderContinuation) {
|
||||||
|
// Subsequent writes can be ignored if the builder has encountered an error.
|
||||||
|
if b.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := len(b.result)
|
||||||
|
b.add(make([]byte, lenLen)...)
|
||||||
|
|
||||||
|
if b.inContinuation == nil {
|
||||||
|
b.inContinuation = new(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.child = &Builder{
|
||||||
|
result: b.result,
|
||||||
|
fixedSize: b.fixedSize,
|
||||||
|
offset: offset,
|
||||||
|
pendingLenLen: lenLen,
|
||||||
|
pendingIsASN1: isASN1,
|
||||||
|
inContinuation: b.inContinuation,
|
||||||
|
}
|
||||||
|
|
||||||
|
b.callContinuation(f, b.child)
|
||||||
|
b.flushChild()
|
||||||
|
if b.child != nil {
|
||||||
|
panic("cryptobyte: internal error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) flushChild() {
|
||||||
|
if b.child == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.child.flushChild()
|
||||||
|
child := b.child
|
||||||
|
b.child = nil
|
||||||
|
|
||||||
|
if child.err != nil {
|
||||||
|
b.err = child.err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
length := len(child.result) - child.pendingLenLen - child.offset
|
||||||
|
|
||||||
|
if length < 0 {
|
||||||
|
panic("cryptobyte: internal error") // result unexpectedly shrunk
|
||||||
|
}
|
||||||
|
|
||||||
|
if child.pendingIsASN1 {
|
||||||
|
// For ASN.1, we reserved a single byte for the length. If that turned out
|
||||||
|
// to be incorrect, we have to move the contents along in order to make
|
||||||
|
// space.
|
||||||
|
if child.pendingLenLen != 1 {
|
||||||
|
panic("cryptobyte: internal error")
|
||||||
|
}
|
||||||
|
var lenLen, lenByte uint8
|
||||||
|
if int64(length) > 0xfffffffe {
|
||||||
|
b.err = errors.New("pending ASN.1 child too long")
|
||||||
|
return
|
||||||
|
} else if length > 0xffffff {
|
||||||
|
lenLen = 5
|
||||||
|
lenByte = 0x80 | 4
|
||||||
|
} else if length > 0xffff {
|
||||||
|
lenLen = 4
|
||||||
|
lenByte = 0x80 | 3
|
||||||
|
} else if length > 0xff {
|
||||||
|
lenLen = 3
|
||||||
|
lenByte = 0x80 | 2
|
||||||
|
} else if length > 0x7f {
|
||||||
|
lenLen = 2
|
||||||
|
lenByte = 0x80 | 1
|
||||||
|
} else {
|
||||||
|
lenLen = 1
|
||||||
|
lenByte = uint8(length)
|
||||||
|
length = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert the initial length byte, make space for successive length bytes,
|
||||||
|
// and adjust the offset.
|
||||||
|
child.result[child.offset] = lenByte
|
||||||
|
extraBytes := int(lenLen - 1)
|
||||||
|
if extraBytes != 0 {
|
||||||
|
child.add(make([]byte, extraBytes)...)
|
||||||
|
childStart := child.offset + child.pendingLenLen
|
||||||
|
copy(child.result[childStart+extraBytes:], child.result[childStart:])
|
||||||
|
}
|
||||||
|
child.offset++
|
||||||
|
child.pendingLenLen = extraBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
l := length
|
||||||
|
for i := child.pendingLenLen - 1; i >= 0; i-- {
|
||||||
|
child.result[child.offset+i] = uint8(l)
|
||||||
|
l >>= 8
|
||||||
|
}
|
||||||
|
if l != 0 {
|
||||||
|
b.err = fmt.Errorf("cryptobyte: pending child length %d exceeds %d-byte length prefix", length, child.pendingLenLen)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.fixedSize && &b.result[0] != &child.result[0] {
|
||||||
|
panic("cryptobyte: BuilderContinuation reallocated a fixed-size buffer")
|
||||||
|
}
|
||||||
|
|
||||||
|
b.result = child.result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) add(bytes ...byte) {
|
||||||
|
if b.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if b.child != nil {
|
||||||
|
panic("cryptobyte: attempted write while child is pending")
|
||||||
|
}
|
||||||
|
if len(b.result)+len(bytes) < len(bytes) {
|
||||||
|
b.err = errors.New("cryptobyte: length overflow")
|
||||||
|
}
|
||||||
|
if b.fixedSize && len(b.result)+len(bytes) > cap(b.result) {
|
||||||
|
b.err = errors.New("cryptobyte: Builder is exceeding its fixed-size buffer")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.result = append(b.result, bytes...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrite rolls back non-negative n bytes written directly to the Builder.
|
||||||
|
// An attempt by a child builder passed to a continuation to unwrite bytes
|
||||||
|
// from its parent will panic.
|
||||||
|
func (b *Builder) Unwrite(n int) {
|
||||||
|
if b.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if b.child != nil {
|
||||||
|
panic("cryptobyte: attempted unwrite while child is pending")
|
||||||
|
}
|
||||||
|
length := len(b.result) - b.pendingLenLen - b.offset
|
||||||
|
if length < 0 {
|
||||||
|
panic("cryptobyte: internal error")
|
||||||
|
}
|
||||||
|
if n < 0 {
|
||||||
|
panic("cryptobyte: attempted to unwrite negative number of bytes")
|
||||||
|
}
|
||||||
|
if n > length {
|
||||||
|
panic("cryptobyte: attempted to unwrite more than was written")
|
||||||
|
}
|
||||||
|
b.result = b.result[:len(b.result)-n]
|
||||||
|
}
|
||||||
|
|
||||||
|
// A MarshalingValue marshals itself into a Builder.
|
||||||
|
type MarshalingValue interface {
|
||||||
|
// Marshal is called by Builder.AddValue. It receives a pointer to a builder
|
||||||
|
// to marshal itself into. It may return an error that occurred during
|
||||||
|
// marshaling, such as unset or invalid values.
|
||||||
|
Marshal(b *Builder) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddValue calls Marshal on v, passing a pointer to the builder to append to.
|
||||||
|
// If Marshal returns an error, it is set on the Builder so that subsequent
|
||||||
|
// appends don't have an effect.
|
||||||
|
func (b *Builder) AddValue(v MarshalingValue) {
|
||||||
|
err := v.Marshal(b)
|
||||||
|
if err != nil {
|
||||||
|
b.err = err
|
||||||
|
}
|
||||||
|
}
|
||||||
+172
@@ -0,0 +1,172 @@
|
|||||||
|
// Copyright 2017 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package cryptobyte contains types that help with parsing and constructing
|
||||||
|
// length-prefixed, binary messages, including ASN.1 DER. (The asn1 subpackage
|
||||||
|
// contains useful ASN.1 constants.)
|
||||||
|
//
|
||||||
|
// The String type is for parsing. It wraps a []byte slice and provides helper
|
||||||
|
// functions for consuming structures, value by value.
|
||||||
|
//
|
||||||
|
// The Builder type is for constructing messages. It providers helper functions
|
||||||
|
// for appending values and also for appending length-prefixed submessages –
|
||||||
|
// without having to worry about calculating the length prefix ahead of time.
|
||||||
|
//
|
||||||
|
// See the documentation and examples for the Builder and String types to get
|
||||||
|
// started.
|
||||||
|
package cryptobyte // import "golang.org/x/crypto/cryptobyte"
|
||||||
|
|
||||||
|
// String represents a string of bytes. It provides methods for parsing
|
||||||
|
// fixed-length and length-prefixed values from it.
|
||||||
|
type String []byte
|
||||||
|
|
||||||
|
// read advances a String by n bytes and returns them. If less than n bytes
|
||||||
|
// remain, it returns nil.
|
||||||
|
func (s *String) read(n int) []byte {
|
||||||
|
if len(*s) < n || n < 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
v := (*s)[:n]
|
||||||
|
*s = (*s)[n:]
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip advances the String by n byte and reports whether it was successful.
|
||||||
|
func (s *String) Skip(n int) bool {
|
||||||
|
return s.read(n) != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadUint8 decodes an 8-bit value into out and advances over it.
|
||||||
|
// It reports whether the read was successful.
|
||||||
|
func (s *String) ReadUint8(out *uint8) bool {
|
||||||
|
v := s.read(1)
|
||||||
|
if v == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
*out = uint8(v[0])
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadUint16 decodes a big-endian, 16-bit value into out and advances over it.
|
||||||
|
// It reports whether the read was successful.
|
||||||
|
func (s *String) ReadUint16(out *uint16) bool {
|
||||||
|
v := s.read(2)
|
||||||
|
if v == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
*out = uint16(v[0])<<8 | uint16(v[1])
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadUint24 decodes a big-endian, 24-bit value into out and advances over it.
|
||||||
|
// It reports whether the read was successful.
|
||||||
|
func (s *String) ReadUint24(out *uint32) bool {
|
||||||
|
v := s.read(3)
|
||||||
|
if v == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
*out = uint32(v[0])<<16 | uint32(v[1])<<8 | uint32(v[2])
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadUint32 decodes a big-endian, 32-bit value into out and advances over it.
|
||||||
|
// It reports whether the read was successful.
|
||||||
|
func (s *String) ReadUint32(out *uint32) bool {
|
||||||
|
v := s.read(4)
|
||||||
|
if v == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
*out = uint32(v[0])<<24 | uint32(v[1])<<16 | uint32(v[2])<<8 | uint32(v[3])
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadUint64 decodes a big-endian, 64-bit value into out and advances over it.
|
||||||
|
// It reports whether the read was successful.
|
||||||
|
func (s *String) ReadUint64(out *uint64) bool {
|
||||||
|
v := s.read(8)
|
||||||
|
if v == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
*out = uint64(v[0])<<56 | uint64(v[1])<<48 | uint64(v[2])<<40 | uint64(v[3])<<32 | uint64(v[4])<<24 | uint64(v[5])<<16 | uint64(v[6])<<8 | uint64(v[7])
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *String) readUnsigned(out *uint32, length int) bool {
|
||||||
|
v := s.read(length)
|
||||||
|
if v == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var result uint32
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
result <<= 8
|
||||||
|
result |= uint32(v[i])
|
||||||
|
}
|
||||||
|
*out = result
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *String) readLengthPrefixed(lenLen int, outChild *String) bool {
|
||||||
|
lenBytes := s.read(lenLen)
|
||||||
|
if lenBytes == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var length uint32
|
||||||
|
for _, b := range lenBytes {
|
||||||
|
length = length << 8
|
||||||
|
length = length | uint32(b)
|
||||||
|
}
|
||||||
|
v := s.read(int(length))
|
||||||
|
if v == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
*outChild = v
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadUint8LengthPrefixed reads the content of an 8-bit length-prefixed value
|
||||||
|
// into out and advances over it. It reports whether the read was successful.
|
||||||
|
func (s *String) ReadUint8LengthPrefixed(out *String) bool {
|
||||||
|
return s.readLengthPrefixed(1, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadUint16LengthPrefixed reads the content of a big-endian, 16-bit
|
||||||
|
// length-prefixed value into out and advances over it. It reports whether the
|
||||||
|
// read was successful.
|
||||||
|
func (s *String) ReadUint16LengthPrefixed(out *String) bool {
|
||||||
|
return s.readLengthPrefixed(2, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadUint24LengthPrefixed reads the content of a big-endian, 24-bit
|
||||||
|
// length-prefixed value into out and advances over it. It reports whether
|
||||||
|
// the read was successful.
|
||||||
|
func (s *String) ReadUint24LengthPrefixed(out *String) bool {
|
||||||
|
return s.readLengthPrefixed(3, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadBytes reads n bytes into out and advances over them. It reports
|
||||||
|
// whether the read was successful.
|
||||||
|
func (s *String) ReadBytes(out *[]byte, n int) bool {
|
||||||
|
v := s.read(n)
|
||||||
|
if v == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
*out = v
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyBytes copies len(out) bytes into out and advances over them. It reports
|
||||||
|
// whether the copy operation was successful
|
||||||
|
func (s *String) CopyBytes(out []byte) bool {
|
||||||
|
n := len(out)
|
||||||
|
v := s.read(n)
|
||||||
|
if v == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return copy(out, v) == n
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty reports whether the string does not contain any bytes.
|
||||||
|
func (s String) Empty() bool {
|
||||||
|
return len(s) == 0
|
||||||
|
}
|
||||||
Vendored
+7
-3
@@ -1183,7 +1183,7 @@ github.com/justinas/alice
|
|||||||
# github.com/kevinburke/ssh_config v1.2.0
|
# github.com/kevinburke/ssh_config v1.2.0
|
||||||
## explicit
|
## explicit
|
||||||
github.com/kevinburke/ssh_config
|
github.com/kevinburke/ssh_config
|
||||||
# github.com/klauspost/compress v1.16.5
|
# github.com/klauspost/compress v1.16.7
|
||||||
## explicit; go 1.18
|
## explicit; go 1.18
|
||||||
github.com/klauspost/compress/flate
|
github.com/klauspost/compress/flate
|
||||||
github.com/klauspost/compress/s2
|
github.com/klauspost/compress/s2
|
||||||
@@ -1332,15 +1332,17 @@ github.com/mschoch/smat
|
|||||||
# github.com/nats-io/jwt/v2 v2.4.1
|
# github.com/nats-io/jwt/v2 v2.4.1
|
||||||
## explicit; go 1.18
|
## explicit; go 1.18
|
||||||
github.com/nats-io/jwt/v2
|
github.com/nats-io/jwt/v2
|
||||||
# github.com/nats-io/nats-server/v2 v2.9.19
|
# github.com/nats-io/nats-server/v2 v2.9.21
|
||||||
## explicit; go 1.19
|
## explicit; go 1.19
|
||||||
github.com/nats-io/nats-server/v2/conf
|
github.com/nats-io/nats-server/v2/conf
|
||||||
github.com/nats-io/nats-server/v2/internal/ldap
|
github.com/nats-io/nats-server/v2/internal/ldap
|
||||||
github.com/nats-io/nats-server/v2/logger
|
github.com/nats-io/nats-server/v2/logger
|
||||||
github.com/nats-io/nats-server/v2/server
|
github.com/nats-io/nats-server/v2/server
|
||||||
|
github.com/nats-io/nats-server/v2/server/certidp
|
||||||
|
github.com/nats-io/nats-server/v2/server/certstore
|
||||||
github.com/nats-io/nats-server/v2/server/pse
|
github.com/nats-io/nats-server/v2/server/pse
|
||||||
github.com/nats-io/nats-server/v2/server/sysmem
|
github.com/nats-io/nats-server/v2/server/sysmem
|
||||||
# github.com/nats-io/nats.go v1.27.0
|
# github.com/nats-io/nats.go v1.28.0
|
||||||
## explicit; go 1.19
|
## explicit; go 1.19
|
||||||
github.com/nats-io/nats.go
|
github.com/nats-io/nats.go
|
||||||
github.com/nats-io/nats.go/encoders/builtin
|
github.com/nats-io/nats.go/encoders/builtin
|
||||||
@@ -1916,6 +1918,8 @@ golang.org/x/crypto/blowfish
|
|||||||
golang.org/x/crypto/cast5
|
golang.org/x/crypto/cast5
|
||||||
golang.org/x/crypto/chacha20
|
golang.org/x/crypto/chacha20
|
||||||
golang.org/x/crypto/chacha20poly1305
|
golang.org/x/crypto/chacha20poly1305
|
||||||
|
golang.org/x/crypto/cryptobyte
|
||||||
|
golang.org/x/crypto/cryptobyte/asn1
|
||||||
golang.org/x/crypto/curve25519
|
golang.org/x/crypto/curve25519
|
||||||
golang.org/x/crypto/curve25519/internal/field
|
golang.org/x/crypto/curve25519/internal/field
|
||||||
golang.org/x/crypto/ed25519
|
golang.org/x/crypto/ed25519
|
||||||
|
|||||||
Reference in New Issue
Block a user