github.com/trustbloc/kms-go@v1.1.2/doc/util/fingerprint/fingerprint.go (about)

     1  /*
     2  Copyright SecureKey Technologies Inc. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package fingerprint
     8  
     9  import (
    10  	"crypto/ecdsa"
    11  	"crypto/ed25519"
    12  	"crypto/elliptic"
    13  	"crypto/rsa"
    14  	"crypto/x509"
    15  	"encoding/binary"
    16  	"errors"
    17  	"fmt"
    18  
    19  	"github.com/btcsuite/btcutil/base58"
    20  
    21  	"github.com/trustbloc/kms-go/doc/jose/jwk"
    22  )
    23  
    24  const (
    25  	// X25519PubKeyMultiCodec for Curve25519 public key in multicodec table.
    26  	// source: https://github.com/multiformats/multicodec/blob/master/table.csv.
    27  	X25519PubKeyMultiCodec = 0xec
    28  	// ED25519PubKeyMultiCodec for Ed25519 public key in multicodec table.
    29  	ED25519PubKeyMultiCodec = 0xed
    30  	// BLS12381g2PubKeyMultiCodec for BLS12-381 G2 public key in multicodec table.
    31  	BLS12381g2PubKeyMultiCodec = 0xeb
    32  	// BLS12381g1g2PubKeyMultiCodec for BLS12-381 G1G2 public key in multicodec table.
    33  	BLS12381g1g2PubKeyMultiCodec = 0xee
    34  	// P256PubKeyMultiCodec for NIST P-256 public key in multicodec table.
    35  	P256PubKeyMultiCodec = 0x1200
    36  	// P384PubKeyMultiCodec for NIST P-384 public key in multicodec table.
    37  	P384PubKeyMultiCodec = 0x1201
    38  	// P521PubKeyMultiCodec for NIST P-521 public key in multicodec table.
    39  	P521PubKeyMultiCodec = 0x1202
    40  
    41  	// RSAPubKeyMultiCodec for RSA public key in multicodec table.
    42  	RSAPubKeyMultiCodec = 0x1205
    43  
    44  	// Default BLS 12-381 public key length in G2 field.
    45  	bls12381G2PublicKeyLen = 96
    46  
    47  	// Number of bytes in G1 X coordinate.
    48  	g1CompressedSize = 48
    49  )
    50  
    51  // CreateDIDKey calls CreateDIDKeyByCode with Ed25519 key code.
    52  func CreateDIDKey(pubKey []byte) (string, string) {
    53  	return CreateDIDKeyByCode(ED25519PubKeyMultiCodec, pubKey)
    54  }
    55  
    56  // CreateDIDKeyByCode creates a did:key ID using the multicodec key fingerprint as per the did:key format spec found at:
    57  // https://w3c-ccg.github.io/did-method-key/#format. It does not parse the contents of 'pubKey'. Use
    58  // kmsdidkey.BuildDIDKeyByKeyType() for marshalled keys extracted from the KMS instead of this function.
    59  func CreateDIDKeyByCode(code uint64, pubKey []byte) (string, string) {
    60  	methodID := KeyFingerprint(code, pubKey)
    61  	didKey := fmt.Sprintf("did:key:%s", methodID)
    62  	keyID := fmt.Sprintf("%s#%s", didKey, methodID)
    63  
    64  	return didKey, keyID
    65  }
    66  
    67  // CreateDIDKeyByJwk creates a did:key ID using the multicodec key fingerprint as per the did:key format spec found at:
    68  // https://w3c-ccg.github.io/did-method-key/#format.
    69  func CreateDIDKeyByJwk(jsonWebKey *jwk.JWK) (string, string, error) { //nolint:gocyclo
    70  	if jsonWebKey == nil {
    71  		return "", "", fmt.Errorf("jsonWebKey is required")
    72  	}
    73  
    74  	switch jsonWebKey.Kty {
    75  	case "EC":
    76  		code, curve, err := ecCodeAndCurve(jsonWebKey.Crv)
    77  		if err != nil {
    78  			return "", "", err
    79  		}
    80  
    81  		switch key := jsonWebKey.Key.(type) {
    82  		case *ecdsa.PublicKey:
    83  			bytes := elliptic.MarshalCompressed(curve, key.X, key.Y)
    84  			didKey, keyID := CreateDIDKeyByCode(code, bytes)
    85  
    86  			return didKey, keyID, nil
    87  		default:
    88  			return "", "", fmt.Errorf("unexpected EC key type %T", key)
    89  		}
    90  	case "OKP":
    91  		var code uint64
    92  
    93  		switch jsonWebKey.Crv {
    94  		case "X25519":
    95  			code = X25519PubKeyMultiCodec
    96  		case "Ed25519":
    97  			code = ED25519PubKeyMultiCodec
    98  		}
    99  
   100  		switch key := jsonWebKey.Key.(type) {
   101  		case ed25519.PublicKey:
   102  			didKey, keyID := CreateDIDKey(key)
   103  
   104  			return didKey, keyID, nil
   105  		case []byte:
   106  			didKey, keyID := CreateDIDKeyByCode(code, key)
   107  
   108  			return didKey, keyID, nil
   109  		default:
   110  			return "", "", fmt.Errorf("unexpected OKP key type %T", key)
   111  		}
   112  	case "RSA":
   113  		key, ok := jsonWebKey.Key.(*rsa.PublicKey)
   114  		if !ok {
   115  			return "", "", fmt.Errorf("unexpected RSA key type %T", jsonWebKey.Key)
   116  		}
   117  
   118  		didKey, keyID := CreateDIDKeyByCode(RSAPubKeyMultiCodec, x509.MarshalPKCS1PublicKey(key))
   119  
   120  		return didKey, keyID, nil
   121  
   122  	default:
   123  		return "", "", fmt.Errorf("unsupported kty %s", jsonWebKey.Kty)
   124  	}
   125  }
   126  
   127  func ecCodeAndCurve(ecCurve string) (uint64, elliptic.Curve, error) {
   128  	var (
   129  		curve elliptic.Curve
   130  		code  uint64
   131  	)
   132  
   133  	switch ecCurve {
   134  	case elliptic.P256().Params().Name, "NIST_P256":
   135  		curve = elliptic.P256()
   136  		code = P256PubKeyMultiCodec
   137  	case elliptic.P384().Params().Name, "NIST_P384":
   138  		curve = elliptic.P384()
   139  		code = P384PubKeyMultiCodec
   140  	case elliptic.P521().Params().Name, "NIST_P521":
   141  		curve = elliptic.P521()
   142  		code = P521PubKeyMultiCodec
   143  	default:
   144  		return 0, nil, fmt.Errorf("unsupported crv %s", ecCurve)
   145  	}
   146  
   147  	return code, curve, nil
   148  }
   149  
   150  // KeyFingerprint generates a multicode fingerprint for pubKeyValue (raw key []byte).
   151  // It is mainly used as the controller ID (methodSpecification ID) of a did key.
   152  func KeyFingerprint(code uint64, pubKeyValue []byte) string {
   153  	multicodecValue := multicodec(code)
   154  	mcLength := len(multicodecValue)
   155  	buf := make([]uint8, mcLength+len(pubKeyValue))
   156  	copy(buf, multicodecValue)
   157  	copy(buf[mcLength:], pubKeyValue)
   158  
   159  	return fmt.Sprintf("z%s", base58.Encode(buf))
   160  }
   161  
   162  func multicodec(code uint64) []byte {
   163  	buf := make([]byte, binary.MaxVarintLen64)
   164  	bw := binary.PutUvarint(buf, code)
   165  
   166  	return buf[:bw]
   167  }
   168  
   169  // PubKeyFromFingerprint extracts the raw public key from a did:key fingerprint.
   170  func PubKeyFromFingerprint(fingerprint string) ([]byte, uint64, error) {
   171  	// did:key:MULTIBASE(base58-btc, MULTICODEC(public-key-type, raw-public-key-bytes))
   172  	// https://w3c-ccg.github.io/did-method-key/#format
   173  	const maxMulticodecBytes = 9
   174  
   175  	if len(fingerprint) < 2 || fingerprint[0] != 'z' {
   176  		return nil, 0, errors.New("unknown key encoding")
   177  	}
   178  
   179  	mc := base58.Decode(fingerprint[1:]) // skip leading "z"
   180  
   181  	code, br := binary.Uvarint(mc)
   182  	if br == 0 {
   183  		return nil, 0, errors.New("unknown key encoding")
   184  	}
   185  
   186  	if br > maxMulticodecBytes {
   187  		return nil, 0, errors.New("code exceeds maximum size")
   188  	}
   189  
   190  	if code == BLS12381g1g2PubKeyMultiCodec {
   191  		// for BBS+ G1G2 did:key type, return the G2 public key only (discard G1 key for now).
   192  		if len(mc[br+g1CompressedSize:]) != bls12381G2PublicKeyLen {
   193  			return nil, 0, errors.New("invalid bbs+ public key")
   194  		}
   195  
   196  		return mc[br+g1CompressedSize:], code, nil
   197  	}
   198  
   199  	return mc[br:], code, nil
   200  }
   201  
   202  // PubKeyFromDIDKey parses the did:key DID and returns the key's raw value.
   203  // note: for NIST P ECDSA keys, the raw value does not have the compression point.
   204  //
   205  //	In order to use elliptic.Unmarshal() with the raw value, the uncompressed point ([]byte{4}) must be prepended.
   206  //	see https://github.com/golang/go/blob/master/src/crypto/elliptic/elliptic.go#L384.
   207  func PubKeyFromDIDKey(didKey string) ([]byte, error) {
   208  	idMethodSpecificID, err := MethodIDFromDIDKey(didKey)
   209  	if err != nil {
   210  		return nil, fmt.Errorf("pubKeyFromDIDKey: MethodIDFromDIDKey: %w", err)
   211  	}
   212  
   213  	pubKey, code, err := PubKeyFromFingerprint(idMethodSpecificID)
   214  	if err != nil {
   215  		return nil, err
   216  	}
   217  
   218  	switch code {
   219  	case X25519PubKeyMultiCodec, ED25519PubKeyMultiCodec, BLS12381g2PubKeyMultiCodec, BLS12381g1g2PubKeyMultiCodec,
   220  		P256PubKeyMultiCodec, P384PubKeyMultiCodec, P521PubKeyMultiCodec:
   221  		break
   222  	default:
   223  		return nil, fmt.Errorf("pubKeyFromDIDKey: unsupported key multicodec code [0x%x]", code)
   224  	}
   225  
   226  	return pubKey, nil
   227  }