1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
|
// Copyright 2024 OpenPubkey
//
// 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.
//
// SPDX-License-Identifier: Apache-2.0
package pktoken
import (
"crypto"
"fmt"
"github.com/lestrrat-go/jwx/v2/jws"
)
// Options configures VerifySignedMessage behavior
type Options struct {
Typ string // Override for the expected typ value
}
type OptionFunc func(*Options)
// WithTyp sets a custom typ value for verification
func WithTyp(typ string) OptionFunc {
return func(o *Options) {
o.Typ = typ
}
}
// NewSignedMessage signs a message with the signer provided. The signed
// message is OSM (OpenPubkey Signed Message) which is a type of
// JWS (JSON Web Signature). OSMs commit to the PK Token which was used
// to generate the OSM.
func (p *PKToken) NewSignedMessage(content []byte, signer crypto.Signer) ([]byte, error) {
cic, err := p.GetCicValues()
if err != nil {
return nil, err
}
pktHash, err := p.Hash()
if err != nil {
return nil, err
}
// Create our headers as defined by section 3.5 of the OpenPubkey paper
protected := jws.NewHeaders()
if err := protected.Set("alg", cic.PublicKey().Algorithm()); err != nil {
return nil, err
}
if err := protected.Set("kid", pktHash); err != nil {
return nil, err
}
if err := protected.Set("typ", "osm"); err != nil {
return nil, err
}
return jws.Sign(
content,
jws.WithKey(
cic.PublicKey().Algorithm(),
signer,
jws.WithProtectedHeaders(protected),
),
)
}
// VerifySignedMessage verifies that an OSM (OpenPubkey Signed Message) using
// the public key in this PK Token. If verification is successful,
// VerifySignedMessage returns the content of the signed message. Otherwise
// it returns an error explaining why verification failed.
//
// Note: VerifySignedMessage does not check this the PK Token is valid.
// The PK Token should always be verified first before calling
// VerifySignedMessage
func (p *PKToken) VerifySignedMessage(osm []byte, options ...OptionFunc) ([]byte, error) {
// Default options
opts := Options{
Typ: "osm", // Default to "osm" for backward compatibility
}
// Apply provided options
for _, opt := range options {
opt(&opts)
}
cic, err := p.GetCicValues()
if err != nil {
return nil, err
}
message, err := jws.Parse(osm)
if err != nil {
return nil, err
}
// Check that our OSM headers are correct
if len(message.Signatures()) != 1 {
return nil, fmt.Errorf("expected only one signature on jwt, received %d", len(message.Signatures()))
}
protected := message.Signatures()[0].ProtectedHeaders()
// Verify typ header matches expected value from options
typ, ok := protected.Get("typ")
if !ok {
return nil, fmt.Errorf("missing required header `typ`")
}
if typ != opts.Typ {
return nil, fmt.Errorf(`incorrect "typ" header, expected %q but received %s`, opts.Typ, typ)
}
// Verify key algorithm header matches cic
if protected.Algorithm() != cic.PublicKey().Algorithm() {
return nil, fmt.Errorf(`incorrect "alg" header, expected %s but received %s`, cic.PublicKey().Algorithm(), protected.Algorithm())
}
// Verify kid header matches hash of pktoken
kid, ok := protected.Get("kid")
if !ok {
return nil, fmt.Errorf("missing required header `kid`")
}
pktHash, err := p.Hash()
if err != nil {
return nil, fmt.Errorf("unable to hash PK Token: %w", err)
}
if kid != string(pktHash) {
return nil, fmt.Errorf(`incorrect "kid" header, expected %s but received %s`, pktHash, kid)
}
_, err = jws.Verify(osm, jws.WithKey(cic.PublicKey().Algorithm(), cic.PublicKey()))
if err != nil {
return nil, err
}
// Return the osm payload
return message.Payload(), nil
}
|